差分约束
1差分约束问题
差分约束形如以下问题。
有n个变量\(x_1,x_2,x_3...x_n\)
有m个不等式,形如:
\[x_i-x_j\le a_k
\]
\(a_i,i\in [1,m]\)是常数
让我们判断是否有解,有解的话输出一组解。
2解法
2.1思路
我们发现如果我们稍微移一下项:
\[x_i\le x_j+a_k
\]
就会发现,这个东西,与我们最短路跑完后的三角形不等式:
\[dist_i\le dist_j+w_{i,j}
\]
十分相似。
所以我们考虑把上述问题转化为最短路问题。
2.2流程
- 把每一个形如:\(x_i-x_j\geq a_k\)的不等式看做一条j到i权为\(a_k\)的有向边。
- 为了便于跑最短路,我们建立一个n+1号节点,从这个节点向每一个点连一条边权为0的边。
为什么这么做是对的呢?我们有以下定理:
- 若\(y_1,y_2,y_3...y_n\)为x的一组解,那么\(y_1-b,y_2-b,y_3-b...y_n-b\) 也是x的一组解。
证明:
显然,把后者带入不等式组后发现常数b全部约掉了。根据假设可知定理成立。证毕。
所以不妨设所有的x都不大于0,让\(x_{n+1}=0\) ,则一定有\(x_i-x_{n+1}\le 0\) 由此可知上述做法正确性。
- 从n+1开始跑spfa,如果有负环,说明无解。
- 否则,\(x_i=d_i,i\in [1,n]\) ,其中 \(d_i\) 是所有点到 \(n+1\) 的距离。
这样做的原因是我们已经知道这个式子可以看做最短路跑完之后的结果,如果没有最短路,自然也就没有解,而如果存在一组 \(d\) ,我们就可以认为他是一组解。
3代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 5010
#define M 10020
using namespace std;
const int INF=0x3f3f3f3f;
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct EDGE{
struct edge{
int to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[M];
int head[N],tail;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
};
EDGE e;
int m,n;
queue<int> q;
bool vis[N];
int cnt[N],d[N];
inline bool spfa(int u){
memset(d,INF,sizeof(d));
q.push(u);vis[u]=1;d[u]=0;
while(!q.empty()){
int top=q.front();q.pop();vis[top]=0;
for(int x=e.head[top];x;x=e.li[x].next){
int to=e.li[x].to;
if(d[to]>d[top]+e.li[x].w){
d[to]=d[top]+e.li[x].w;
cnt[to]=cnt[top]+1;
if(cnt[to]>=n+1) return 0;
if(!vis[to]) q.push(to),vis[to]=1;;
}
}
}
return 1;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
int x1=read(),x2=read(),y=read();
e.add(x2,x1,y);
}
for(int i=1;i<=n;i++) e.add(n+1,i,0);
if(!spfa(n+1)){
printf("NO\n");
return 0;
}
for(int i=1;i<=n;i++) printf("%d ",d[i]);
return 0;
}