差分约束学习笔记
对于多个形如 x - y <= a 的不等式
我们将其变形 变成 x <= y + a
如果由 y 到 x 建一条边权为 a 的边然后跑最短路
那么 dis[x] <= dis[y] + a 恒成立 得到一组通解
若图中出现了负环 说明无最短路 进而说明无解
并且为了维护图的连通性 可以加一个超级原点 对所有的点连一条边权为 0 的边 然后作为起点跑最短路
当然可以用spfa 这里另外介绍一种基于spfa的bellman-ford算法
首先对于spfa判负环 在不考虑重边的情况下 我们判的是松弛次数 如果松弛次数 >= n 即为有负环
而bellman-ford的算法的原理是 n次对所有点进行松弛操作 若最后一次还有点可以被松弛 就证明有负环存在
code:
bool bellman_ford(int s) {
memset(dis, 0x3f, sizeof dis );
dis[s] = 0;
bool flag;
for (int i = 1; i <= n; ++i) {
flag = 0;
for (int j = 1; j <= n; ++j) {
for (int k = head[j]; k; k = nxt[k]) {
int y = to[k];
if (dis[y] > dis[j] + len[k]) {
dis[y] = dis[j] + len[k];
flag = 1;
}
}
}
if (flag == 0) break;
}
return flag;
}
当然 这种做法会出现一些小小的问题
因为我们钦定 dis[s] = 0 并且由 s 向每个点都建了一条边权为 0 的边
那么实际上对于每个点 i 都有 dis[i] <= dis[s] + 0 即 dis[i] <= 0
这时我们求出来的所有值都是 <= 0 的
但是很多时候我们要求的是要让所有值 >= 0
这里有两种解决办法:
- 求出最小的那个负值 然后全体减掉这个负值
- 把 x - y <= a 变形为 y >= a + x 然后跑最长路 这样对于每个点都有 dis[i] >= dis[s] + 0 即 dis[i] >= 0
至于具体用哪种 就见仁见智了
例题:P1260 工程规划
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 0721;
int head[N], nxt[N], to[N], len[N], cnt;
int dis[N], vis[N];
bool exist[N];
int n, m;
inline void cmb(int x, int y, int z) {
to[++cnt] = y;
len[cnt] = z;
nxt[cnt] = head[x];
head[x] = cnt;
}
bool bellman_ford(int s) {
memset(dis, 0x3f, sizeof dis );
dis[s] = 0;
bool flag;
for (int i = 1; i <= n; ++i) {
flag = 0;
for (int j = 1; j <= n; ++j) {
for (int k = head[j]; k; k = nxt[k]) {
int y = to[k];
if (dis[y] > dis[j] + len[k]) {
dis[y] = dis[j] + len[k];
flag = 1;
}
}
}
if (flag == 0) break;
}
return flag;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
cmb(y, x, z);
}
for (int i = 1; i <= n; ++i) cmb(0, i, 0);
if (bellman_ford(0))
printf("NO SOLUTION");
else {
int minn = 0x7fffffff;
for (int i = 1; i <= n; ++i) minn = min(minn, dis[i]);
for (int i = 1; i <= n; ++i) printf("%d\n",dis[i] - minn);
}
return 0;
}