D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束

D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束_哔哩哔哩_bilibili

 

差分约束题目列表 - 洛谷

P5960 【模板】差分约束 - 洛谷

差分约束系统是给 $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]<<' ';
}

 

P1993 小 K 的农场 - 洛谷

有 $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";
}

 

posted @ 2026-03-26 17:22  董晓  阅读(157)  评论(0)    收藏  举报