概念
如果一个系统由 $n$ 个变量和 $m$ 个约束条件组成,其中每个约束条件形如 $x_i - x_j <= c_k$,其中 $c_k$ 是常数(可以是负数,也可以是非负数),则称其为差分约束系统。我们要解决的问题是:求一组解 $x_1= a-1, x_2 = a_2,...,x_n = a_n$,使得所有的约束条件得到满足,否则判断无解。
差分约束系统中的每个约束条件 $x_i - x_j leq c_k$ 都可以变形成 $x_i leq c_k + x_j$,这与单源最短路中的三角形不等式 $dis[y] leq dis[x] + w$ 非常相似。因此,我们可以把每个变量 $x_i$ 看作图中的一个节点,对于每个约束条件 $x_i - x_j leq c_k$,从节点 $j 向节点 $i 连一条长度为 $c_k$ 的有向边。
注意到,如果 ${a_1,a_2,...a_n }$是该差分约束系统的一组解,那么对于任意的常数 $d$,${a_1+d, a_2+d, ..., a_n+d }$ 显然也是该差分约束系统的一组解。
设 $dis[0]=0$ 并向每个点连一条边,跑单源最短路算法。若图中存在负环,则给定的差分约束系统无解,否则,$x_i = dis[i]$ 为该差分约束系统的一组解。
一般使用 $Belllman-Ford$ 或队列优化的 $Bellman-Ford$ (俗称 $SPFA$)判断图中是否存在负环,最坏的时间复杂度为 $O(nm)$.
例题
题意:
求解差分约束系统,有 $m$ 条约束条件,每条形如 $x_a-x_bgeq c_k$,$x_a - x_b leq c_k$,或 $x_a=x_b$ 的形式,判断该差分系统有没有解。
分析:
对于 $x_a = x_b$ 可以拆成等价的 $x_a leq x_b$ 和 $x_a geq x_b$.
建图,然后跑单源最短路判断是否存在负环即可。
Bellman-Ford版很慢,用时11.77s
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll INF = 1LL << 61; const int maxv = 4*10000 + 10; //最大顶点数 const int maxe = 4*10000 + 10; //最大边数 //至少3倍空间 ll dis[maxv]; struct Edge { int u, v, w; }edge[maxe]; int tot; int n, m; void AddEdge(int u, int v, int w) { edge[tot].u = u, edge[tot].v = v, edge[tot].w = w; tot++; } bool Bellman_Ford(int s) { //memset(dis, INF, sizeof(dis)); for(int i = 0;i <= n;i++) dis[i] = INF; dis[s] = 0; for (int k = 1; k <= n - 1; k++) //迭代n-1次 for (int i = 0; i < tot; i++) //检查每条边 { int u = edge[i].u, v = edge[i].v, w = edge[i].w; if (dis[u] + w < dis[v]) dis[v] = dis[u] + w; } for (int i = 0; i < tot; i++) if (dis[edge[i].v] > dis[edge[i].u] + edge[i].w) return false; return true; } int main() { scanf("%d%d", &n, &m); for(int i = 0;i < m;i++) { int order, a, b, c; scanf("%d", &order); if(order == 1) { scanf("%d%d%d", &a, &b, &c); AddEdge(a, b, -c); } else if(order == 2) { scanf("%d%d%d", &a, &b, &c); AddEdge(b, a, c); } else { scanf("%d%d", &a, &b); AddEdge(a, b, 0); AddEdge(b, a, 0); } } for(int i = 1;i <= n;i++) AddEdge(0, i, 0); //保证图的联通,权值随便 n++; //增加源点0 if(Bellman_Ford(0)) printf("Yes "); else printf("No "); // for(int i = 1;i < n;i++) printf("%d: %d ", i, dis[i]); 可以输出一组解 return 0; }
DFS-SPFA版的,很适合用来判断负环,0 ms(虽然时间复杂度极度不稳定)
#include <cstdio> const int maxn = 1e4+4; const int inf = 0x7fffffff; int n, m; struct e{ int to, nxt, w; }e[maxn<<3]; int head[maxn], en; void add(int u, int v, int w){ e[++en].to = v; e[en].w = w; e[en].nxt = head[u]; head[u] = en; } int vis[maxn], dis[maxn], spfa_flag; void spfa(int u){ if(spfa_flag) return; vis[u] = 1; for(int i = head[u]; i; i = e[i].nxt){ int v = e[i].to; if(dis[v] > dis[u] + e[i].w){ if(vis[v]){ spfa_flag = 1; return; } dis[v] = dis[u] + e[i].w; spfa(v); } } vis[u] = 0; } int op, a, b, c; int main(){ scanf("%d%d", &n, &m); for(int i = 1; i <= m; i++){ scanf("%d", &op); if(op==3){ scanf("%d%d", &a, &b); add(a, b, 0); add(b, a, 0); } if(op==1){ scanf("%d%d%d", &a, &b, &c); add(a, b, -c); } if(op==2){ scanf("%d%d%d", &a, &b, &c); add(b, a, c); } } //for(int i = 1; i <= n; i++) add(n+1, i, 0); for(int i = 1; i <= n; i++) dis[i] = 0; for(int i = 1; i <= n; i++) if (!vis[i]) spfa(i); if(spfa_flag) printf("No "); else printf("Yes "); return 0; }
参考链接:
1. https://oi-wiki.org/graph/diff-constraints/#luogu-p1993-k