差分约束系统详解

差分约束系统

差分约束系统即给一些限制条件,问你满足条件的最优解(最大最小值),或者判断添加你是否成立。

解的存在性

差分约束系统中,有两种特殊情况,1种是出现负权圈时, 如下图,求(arr[t] - arr[s])的最大值, 这时 s 到 t 不存在最短路, 即最大值不纯在。

还有一种情况是 s 和 t 没有约束关系,如下图所示。这时他们得最大值为正无穷最小值为负无穷。

即解的情况有三种:有解, 无解, 无限多解。

不等式标准化

在求解差分约束系统的最优解时, 我们需要将不等式标准化。这时我们要根据题目中的要求做相应变换。如果题目要求最大值,我们则将不等式化作(a[i] - a[j] <= t),建一条 j --> i 的权值为t边, 并求最短路;反之要求最小值,我们则将不等式化作(a[i] - a[j] >= t), 建一条 j --> i 的权值为t的边, 并求最长路。

还有需要建的几种特殊的边。如果题目约束 (a[i] - a[j] == t),那么我们可以将式子转化成下面两个式子:

[a[i] - a[j] >= t\ a[i] - a[j] <= t ]

再将其中一种不等号反向, 建图即可。

同时如果题目要求(a[i] - a[j] < t), 那么在整数条件下我们可以将其改为(a[i] - a[j] <= t - 1),实数的话加个eps即可。

ps:在判断有无负环时, 我们用dfs比较好, 因为用bfs可能会mle或tle。

例题

模板题

【描述】:n 个未知数, m 个不等式形如(a - b >= c), 有解则输出一组解, 无解则输出NO。

【思路】:直接差分约束, 用一下超级源点的思想从0给所有未知数加个权值为0的边, 最后泡一下spfa(0),有解则dis[i]就是满足要求的每个未知数的解。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 4e5 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b)    { return b == 0 ? a : gcd(b, a % b); }

struct farm{
    int nex, to, val;
}Edge[maxn<<1];
int head[maxn], n, m, tot;
queue<int> q;
int vis[maxn], dis[maxn], mk[maxn];

inline void init(){
    tot = 0;
    memset(head, -1, sizeof(head));
}
inline void add(int from, int to, int val){
    Edge[++tot].to = to;
    Edge[tot].val = val;
    Edge[tot].nex = head[from];
    head[from] = tot;
}
bool spfa(int s)
{
    for(int i = 1; i <= n; ++i){
        mk[i] = vis[i] = 0;
        dis[i] = i == s ? 0 : inf;
    }
    q.push(s);
    vis[s] = 1; mk[s]++;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u]; i != -1; i = Edge[i].nex){
            int v = Edge[i].to;
            if(dis[v] > dis[u] + Edge[i].val){
                dis[v] = dis[u] + Edge[i].val;
                if(vis[v])  continue;
                vis[v] = 1;
                if(++mk[v] >= n)    return 0;
                q.push(v);
            }
        }
    }
    return 1;
}

int main()
{
    scanf("%d %d", &n, &m);
    init();
    for(int i = 1; i <= m; ++i){
        int a, b, val;
        scanf("%d %d %d", &a, &b, &val);
        add(b, a, val);
    }
    for(int i = 0; i <= n; ++i){
        add(0, i, 0);
    }
    int ok = spfa(0);
    if(!ok) puts("NO");
    else{
        for(int i = 1; i <= n; ++i){
            printf("%d%c", dis[i], i == n ? '
' : ' ');
        }
    }
    system("pause");
}

P1250种树

【描述】:n 个区域编号 1...n,m 个房子,每个房子都有一个要求:在s ~ t 区域内至少种 val 棵树, 问最少种即可树满足要求?

【思路】:这题求得是最小值, 我们可以将不等式化成 ">=" 的形式, 用前缀和来表示, a - b中至少有val棵树, 即:(sum[a] - sum[b-1] >= val), 故可建 (b-1) --> a 的权值为 val 的边, 同时还有一个约束条件:(1 >= sum[i] - sum[i-1] >= 0),即:(sum[i] - sum[i-1] >= 0), (sum[i-1] - sum[i] >= -1);即建(i - 1) --> i的权值为0的边和i --> (i-1)的权值为-1的边, 最后spfa跑一边最长路, 输出dis[n]即可。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b)    { return b == 0 ? a : gcd(b, a % b); }

int n, m;
int dis[maxn], vis[maxn], tot;
struct edge{
    int nex, to, val;
}Edge[maxn];
int head[maxn];

inline void add(int from, int to, int val){
    Edge[++tot].to = to;
    Edge[tot].val = val;
    Edge[tot].nex = head[from];
    head[from] = tot;
}
void spfa(int s)
{
    for(int i = 0; i <= n; ++i){
        dis[i] = -inf;
        vis[i] = 0;
    }
    queue<int> q;
    q.push(s);
    dis[s] = 0; vis[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u]; i != -1; i = Edge[i].nex){
            int v = Edge[i].to;
            if(dis[v] < dis[u] + Edge[i].val){
                dis[v] = dis[u] + Edge[i].val;
                if(vis[v])  continue;
                vis[v] = 1;
                q.push(v);
            }
        }
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= m; ++i){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        add(a-1, b, c);
    }
    for(int i = 1; i <= n; ++i){
        add(i - 1, i, 0);
        add(i, i - 1, -1);
    }
    spfa(0);
    printf("%d
", dis[n]);
    system("pause");
}

P1993 小K的农场

【描述】:n 个农场, m个不等式, 1开头表示 a 比 b 至少多 val 个, 2开头表示a 比 b 至多多多少个, 3开头表示 a 和 b 相等, 问有没有解。n, m 都是 1e4 范围。

【思路】:差分约束判断负环即可。spfa判断负环时间复杂度为(O(nm)), 在这可能会超时, 所以用dfs模拟一下即可。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b)    { return b == 0 ? a : gcd(b, a % b); }

struct edge{
    int nex, to, val;
}Edge[maxn];
int head[maxn], tot, n, m;
int dis[maxn], mk[maxn], vis[maxn];

inline void add(int from, int to, int val){
    Edge[++tot].to = to;
    Edge[tot].val = val;
    Edge[tot].nex = head[from];
    head[from] = tot;
}
bool spfa(int u){
    vis[u] = 1;
    for(int i = head[u]; i != -1; i = Edge[i].nex){
        int v = Edge[i].to;
        if(dis[v] < dis[u] + Edge[i].val){
            dis[v] = dis[u] + Edge[i].val;
            if(vis[v])  return 0;
            if(!spfa(v)) return 0;
        }
    }
    vis[u] = 0;
    return 1;
}

int main()
{
    scanf("%d %d", &n, &m);
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= m; ++i){
        int a, b, val, f;
        scanf("%d %d %d", &f, &a, &b);
        if(f == 1){
            scanf("%d", &val);
            add(b, a, val);
        }
        else if(f == 2){
            scanf("%d", &val);
            add(a, b, -val);
        }
        else{
            add(b, a, 0);
            add(a, b, 0);
        }
    }
    for(int i = 1; i <= n; ++i){
        add(0, i, 0);
        dis[i] = -inf;
    }
    if(spfa(0)) puts("Yes");
    else puts("No");
    system("pause");
}
原文地址:https://www.cnblogs.com/StungYep/p/12345611.html