D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束
D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束_哔哩哔哩_bilibili
差分约束系统是给 $m$ 个的 $n$ 元一次不等式,不等式形如 $x_i-x_j\le c_k$,其中 $c_k$ 是常数(0,正或负)
求一组解 $\{x_1,x_2,...,x_n\}=\{a_1,a_2,...,a_n\}$,使得所有不等式(约束条件)得到满足,否则判断无解
不等式 $x_i-x_j\le c_k$ 可以变成 $x_i\le x_j+c_k$,这类似单源最短路中的三角形不等式 $d_v\le d_u+w$
图中,沿 $s-u-v$ 走,$d_v=d_u+w$,将来进一步松弛,$d_v<d_u+w$

因此,我们把每个变量 $x$ 看做一个点,每个不等式 $x_i\le x_j+c_k$,从点 $x_j$ 向点 $x_i$ 连一条长度为 $c_k$ 的有向边
跑 SPFA,若有负环,则无解,因为负环不存在最短路;若无负环,则 $\{d_1,d_2,...,d_n\}$ 就是一组可行解
注意:① 跑 SPFA 先把所有点均入队,避免走不到环. 或者从超级源点 0 向各点连 0 边权的边,从 0 点开始跑
② 可行解是点之间的相对距离,d 数组初始值均取 0 即可

总结:差分约束求解不等式组有两种方法:最短路和最长路
求最大解:条件转化为 $a-b\le c$,跑最短路,判负环
求最小解:条件转化为 $a-b\ge c$,跑最长路,判正环
相关板子:
D113【模板】最短路→负环 SPFA 算法 P2850 [USACO06DEC] Wormholes G 虫洞 - 董晓 - 博客园
// 差分约束 SPFA 算法 O(NM) #include<bits/stdc++.h> using namespace std; const int N=5005,M=5005; int idx,h[N],to[M],ww[M],ne[M]; void add(int a,int b,int c){ to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx; } int n,m; int d[N],cnt[N]; bool vis[N]; bool spfa(){ memset(d,0,sizeof d); //因为求相对距离 memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); queue<int> q; for(int i=1; i<=n; i++) q.push(i),vis[i]=true; //均入队 while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=h[u]; i; i=ne[i]){ int v=to[i]; if(d[v]>d[u]+ww[i]){ d[v]=d[u]+ww[i]; //最短路 cnt[v]=cnt[u]+1; //记录走过的边数 if(cnt[v]==n) return true; //有负环 if(!vis[v]) q.push(v), vis[v]=true; } } } return false; //无负环 } int main(){ cin>>n>>m; for(int i=1,u,v,w; i<=m; i++){ cin>>u>>v>>w; add(v,u,w); //u-v<=w } if(spfa()) cout<<"NO"; else for(int i=1; i<=n; i++) cout<<d[i]<<' '; }
// 差分约束 SPFA 算法 O(NM) #include<bits/stdc++.h> using namespace std; const int N=5005,M=5005; int idx,h[N],to[M],ww[M],ne[M]; void add(int a,int b,int c){ to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx; } int n,m; int d[N],cnt[N]; bool vis[N]; bool spfa(){ memset(d,0,sizeof d); //因为求相对距离 memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); queue<int> q; for(int i=1; i<=n; i++) q.push(i),vis[i]=true; //均入队 while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=h[u]; i; i=ne[i]){ int v=to[i]; if(d[v]<d[u]+ww[i]){ d[v]=d[u]+ww[i]; //最长路 cnt[v]=cnt[u]+1; //记录走过的边数 if(cnt[v]==n) return true; //有正环 if(!vis[v]) q.push(v), vis[v]=true; } } } return false; //无正环 } int main(){ cin>>n>>m; for(int i=1,u,v,w; i<=m; i++){ cin>>u>>v>>w; //u-v<=w add(u,v,-w); //v-u>=-w } if(spfa()) cout<<"NO"; else for(int i=1; i<=n; i++) cout<<d[i]<<' '; }
有 $m$ 条约束条件,每条形如 $a-b\ge c,a-b\le c,或\;a=b$ 的形式,判断该差分约束系统有没有解.
思路
方法1:条件均转化为 $\le$,跑最短路,判负环
1. $a-b\ge c \color{red}{\longrightarrow} b-a\le -c \color{red}{\longrightarrow} add(a,b,-c)$
2. $a-b\le c \color{red}{\longrightarrow} a-b\le c \color{red}{\longrightarrow} add(b,a,c)$
3. $a=b \color{red}{\longrightarrow} a-b\le 0,b-a\le 0 \color{red}{\longrightarrow} add(b,a,0),add(a,b,0)$
方法2:条件均转化为 $\ge$,跑最长路,判正环
// 差分约束 SPFA 算法 O(NM) #include<bits/stdc++.h> using namespace std; const int N=5005,M=10005; int idx,h[N],to[M],ww[M],ne[M]; void add(int a,int b,int c){ to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx; } int n,m; int d[N],cnt[N]; bool vis[N]; bool spfa(){ memset(d,0,sizeof d); //相对距离 memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); queue<int> q; for(int i=1; i<=n; i++) q.push(i),vis[i]=true; while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=h[u]; i; i=ne[i]){ int v=to[i]; if(d[v]>d[u]+ww[i]){ d[v]=d[u]+ww[i]; //最短路 cnt[v]=cnt[u]+1; //记录走过的边数 if(cnt[v]==n) return true; //有负环 if(!vis[v]) q.push(v),vis[v]=true; } } } return false; //无负环 } int main(){ cin>>n>>m; for(int i=1,op,a,b,c; i<=m; i++){ cin>>op>>a>>b; if(op==1) cin>>c,add(a,b,-c); //a-b≥c else if(op==2) cin>>c,add(b,a,c);//a-b≤c else add(a,b,0),add(b,a,0); //a=b } if(spfa()) cout<<"No"; else cout<<"Yes"; }
// 差分约束 SPFA 算法 O(NM) #include<bits/stdc++.h> using namespace std; const int N=5005,M=10005; int idx,h[N],to[M],ww[M],ne[M]; void add(int a,int b,int c){ to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx; } int n,m; int d[N],cnt[N]; bool vis[N]; bool spfa(){ memset(d,0,sizeof d); //相对距离 memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); queue<int> q; for(int i=1; i<=n; i++) q.push(i),vis[i]=true; while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=h[u]; i; i=ne[i]){ int v=to[i]; if(d[v]<d[u]+ww[i]){ d[v]=d[u]+ww[i]; //最长路 cnt[v]=cnt[u]+1; //记录走过的边数 if(cnt[v]==n) return true; //有正环 if(!vis[v]) q.push(v),vis[v]=true; } } } return false; //无正环 } int main(){ cin>>n>>m; for(int i=1,op,a,b,c; i<=m; i++){ cin>>op>>a>>b; if(op==1) cin>>c,add(b,a,c); //a-b≥c else if(op==2) cin>>c,add(a,b,-c);//a-b≤c else add(a,b,0),add(b,a,0); //a=b } if(spfa()) cout<<"No"; else cout<<"Yes"; }
浙公网安备 33010602011771号