差分约束
概念
如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj\(\leqslant\)k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
求解方法
求解差分约束系统,可以转化成图论的单源最短路径(或最长路径)问题。
观察ai-aj<=w,会发现它类似最短路中的三角不等式d[v]<=d[u]+w[u,v],即d[v]-d[u]<=w[u,v]。因此,以每个变量ai为结点,对于约束条件ai-aj<=w,连接一条边(j,i),边权为w。(这为最短路),同样最长路 ai - aj >= w,连接一条边(j,i),边权为-w.
判断所求最长路or最短路:
x \(\leqslant\) 3
x \(\leqslant\) 5
x \(\leqslant\) -1
那么这个不等式的解应为 :x \(\leqslant\) -1
是为最短路,反之不等式同大取大,为最长路。
注意
1.不等式组转化成的图可能含有负权边,即可能会有负环,所以要用SPFA算法判环。
2.需要引入虚拟源点(超级源点),共n+1个点。
3.最短路判负环,最长路判正环 。SPFA算法中需要加一个num[]数组来记录当前访问过的结点数,如果在更新完某条边后结点数大于等于n,则说明有环. 无解就是有环的情况。
4.建立一个虚拟(又称超级)源点,以虚拟源点对所有点连一条边权为0的点,保证图的连通。
例题一
luogu P1260 工程规划
分析
读懂题意我们发现就是一个差分约束系统,至于非负整数解,求出答案中的最小值,把每个答案减去这个最小值即可。
输入
5 8
1 2 0
1 5 -1
2 5 1
3 1 5
4 1 4
4 3 -1
5 3 -1
5 4 -3
输出
0
2
5
4
1
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <limits.h>
using namespace std;
typedef long long ll;
const int N=2e6;
struct edge{
int from;
int to;
int dis;
int next;
}e[N];
int head[N],cnt;
void add(int u,int v,int w)
{
++cnt;
e[cnt].from = u;
e[cnt].to = v;
e[cnt].dis = w;
e[cnt].next = head[u];
head[u] = cnt;
}
int mapp[N];
int num[N];
bool vis[N];
int n,m,u,v,w;
queue<int> q;
void SPFA(int u)
{
int minn = INT_MAX;
memset(mapp,0x3f,sizeof(mapp));
memset(vis,0,sizeof(vis));
mapp[u] = 0; vis[u] = true;
q.push(u);
while(!q.empty())
{
int x = q.front();
vis[x] = false;
q.pop();
for(int i=head[x];i;i=e[i].next)
{
int y = e[i].to;
if(mapp[y] > mapp[x] + e[i].dis)
{
mapp[y] = mapp[x] + e[i].dis;
if(vis[y] == false)
{
vis[y] = true;
num[y]++;
q.push(y);
}
if(num[y] >= n)
{
puts("NO SOLUTION");
return;
}
}
}
}
for(int i=1;i<=n;i++) minn = min(minn,mapp[i]);
for(int i=1;i<=n;i++) printf("%d\n",mapp[i]-minn);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(v,u,w);
}
for(int i=1;i<=n;i++) add(n+1,i,0);
SPFA(n+1);
return 0;
}