差分约束系统
差分约束系统是最短路的一类经典应用。如果一个不等式组由n个变量和m个约束条件组成,且每个约束条件都是形如 i-j≤k,1≤i≤n,1≤j≤n的不等式,则称其为 差分约束系统 (system of differenceconstraints)。差分约束系统是求解一组变量的不等式组的算法。
问题转化
我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。
对于不等式中的其中一组j−i≤k,我们会发现它类似最短路网络(全部由最短路上的边组成的子图)中的三角不等式d[v]−d[u]≤w<u,v>,即x[u]+w<u,v>≥x[v],所以我们可以理解成从顶点d[u]到顶点d[v]连一条权值为w<u,v>的边,用最短路算法得到最短路的答案x[i],也就求出了原不等式组的一个解。
因此我们可以将每个变量i作为一个顶点,对于约束条件x[j]-x[i]≤k,连接一条边权为k的有向边<i,j>。我们再增加一个超级源s,s连向其余每个顶点,边权均为0。如果用x[i]表示s到i的最短路, 用x[j]表示s到j的最短路,在求最短路的时候,由于边<i,j>的存在,那么一定有x[j]≤x[i]+k,自然就满足了我们的不等式了。
如果程序正常结束,那么得到的最短路答案数组d[i]就是满足条件的一组x[i]的解。
若图中存在负环,则该不等式组无解。比如存在如下一个负环
对应的不等式组为:
x[2]≤x[1]−3
x[3]≤x[2]−4
x[1]≤x[3]+4
经过替换,最终得到[1]≤x[1]−3,这是不可能成立的。所以一个负环实际上对应了一组矛盾的不等式。
对于下面的不等式组,我们建出的图如下。
x
两种连边方法
第一种是连边后求最短路的方法,对于x[j]−x[i]≤k,变形为x[j]≤x[i]+k,从i到j连一条权值为k的边。若加入超级源点,最后求出最短路,实际上表示在x[i]≤0 的情况下,所有x的 最大的解。若存在负环,就无解。
第二种是连边后求最长路的方法,对于x[j]−x[i]≤k,变形为x[i]≥x[j]−k,从j到i连一条权值为-k的边。若加入超级源点,最后求最长路,实际上表示在x[i]≥0 的情况下,所有x的 最小的解。若存在正环,就无解。
一道题
第一行有两个数字n,m分别表示有n个变量m个表达式。
接下来会有m行,每行有四个数字。第一个数字表示是这个表达式是大于等于,还是小于等于,或者是等于。后面的三个数字就对应上面表达式中的数字。
1:≤
2:≥
3:=
计算最小值就是计算最长路问题
直接上代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
struct edge {
int v, w, fail;
edge() {}
edge(int _v, int _w, int _fail) {
v = _v;
w = _w;
fail = _fail;
}
} e[M << 1];
int head[N], len;
void init() {
memset(head, -1, sizeof(head));
len = 0;
}
void add(int u, int v, int w) {
e[len] = edge(v, w, head[u]);
head[u] = len++;
}
void add2(int u, int v, int w) {
add(u, v, w);
add(v, u, w);
}
int n, m;
int dis[N], in[N];
bool vis[N];
bool spfa(int u) {
memset(vis, false, sizeof(vis));
vis[u] = true;
memset(dis, -1, sizeof(dis));
dis[u] = 0;
memset(in, 0, sizeof in);
in[u] = 1;
queue<int> q;
q.push(u);
while (!q.empty()) {
u = q.front();
q.pop();
vis[u] = false;
for (int j = head[u]; ~j; j = e[j].fail) {
int v = e[j].v;
int w = e[j].w;
if (dis[v] < dis[u] + w) { // 求最长路,和求最短路相反
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = true;
++in[v];
if (in[v] > n + 1) {
return true;
}
}
}
}
}
return false;
}
int main() {
init();
int u, v, w, op;
cin >> n >> m;
while (m--) {
cin >> op;
cin >> u >> v >> w;
if (op == 1) {
add(u, v, -w);
}
else if (op == 2) {
add(v, u, w);
}
else {
add(u, v, -w);
add(v, u, w);
}
}
for (int i = 1; i <= n; ++i) {
add(0, i, 0);
}
if (spfa(0)) {
cout << "no" << endl;
} else {
for (int i = 1; i <= n; ++i) {
cout << "x" << i << " = " << dis[i] << endl;
}
}
return 0;
}