当前位置: 首页 > news >正文

差分约束系统SPFA判负环

什么是差分约束系统?

差分约束可以理解为满足n个条件的不等式。通常属于

为什么能用spfa?

1.每个不等式能转化为一条有向边,比如X\: j-X\: i>=n可以转化为从节点i到j有一条权为n的边

2.求解差分约束系统等价于寻找一组变量值 x_1, x_2, ..., x_n,满足所有边对应的约束。这与最短路松弛操作的逻辑完全一致:

最短路中,dist[j] ≤ dist[i] + c(从 i 到 j 的最短路径不超过 i 的距离加边权 c

差分约束中,x_j ≤ x_i + c(变量 x_j 不超过 x_i 加常数 c

———————————————————————————————————————————

如何根据不等式建边?

对于任意不等式 x_j - x_i ≤ c(这是最基础的形式):

1.等价变形为 x_j ≤ x_i + c

2.对应图中一条从节点 i 指向节点 j 的有向边,边的权重为 c

为什么如此?

因为spfa的松弛操作对应“如果dist[i]>d[x]+w[i]就更新”,这个不等式相当于x_j ≤ x_i + c

所有建边规则的本质,都是把差分约束的不等式,统一转化为最短路算法能理解的 “dist[j] ≤ dist[i] + c” 形式。只有这样,才能通过 SPFA 等最短路算法,既找到满足所有约束的x(即dist数组),又能检测负环(判断无解)。

——————————————————————————————————————————

为什么负环无解?

在差分约束系统中,负环的存在会导致无限满足 “变量差值小于某个负数” 的矛盾循环,使得变量取值不存在合法解。​

具体来说,差分约束将每个不等式 x_j - x_i ≤ c 转化为图中从 i 到 j、边权为 c 的有向边,求解等价于找一组变量值满足所有边的约束。若图中存在负环(环上所有边权之和为负数),则沿着环循环遍历会得到矛盾:​

  • 例如负环 a→b→c→a,边权分别为 -1、-1、-1,对应约束 x_b - x_a ≤ -1、x_c - x_b ≤ -1、x_a - x_c ≤ -1。​
  • 将三个不等式相加,左边抵消为 0,右边总和为 -3,得到 0 ≤ -3,这是恒不成立的矛盾。​
  • 同时,绕负环循环次数越多,变量差值会无限减小(如 x_b 会比 x_a 无限小),无法找到固定的合法数值,因此系统无解。

——————————————————————————————————————————

如何spfa判负环?

判断任意一个节点入队次数是否大于总节点数。因为存在负环会让spfa判断两点间权时每经过一次就让总和越变越小,于是重复更新,重复入队

来看代码

bool spfa(int s1) {for(int i = 1; i <= n+1; i++) {dis[i] = 2147483647;cnt[i] = 0;inq[i] = false;}dis[s1] = 0;q.push(s1);inq[s1] = true;cnt[s1]++;int x;while(!q.empty()) {x = q.front();q.pop();inq[x] = false;for(int i = head[x]; i; i = ne[i]) {if(dis[to[i]] > dis[x] + w[i]) {dis[to[i]] = dis[x] + w[i];if(!inq[to[i]]) {q.push(to[i]);inq[to[i]] = true;cnt[to[i]]++;if(cnt[to[i]] > n) {return true;}}}}}return false;
}

很常规,如果你不知道基础的spfa,可以看一下前几期的博客最短路spfa和多层图(P1073 [NOIP 2009 提高组] 最优贸易)题解-CSDN博客

———————————————————————————————————————————

很好,既然如此,我们来写一道模板

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

先来看一下如何建边,我们得到的等式是差小于一个常数。根据我们上面梳理的内容,我们要先转化为一个数小于等于一个和。也就是x_c1≤x_c1'+y_1

令x_c1=a,x_c1'=b,y1=c,所以我们对于第一个等式,建边就是add(b,a,c)

同时,我们要建立一个“超级节点”,确保这个节点和整个图上的所有节点都有边相连,因为图不一定是连通图,spfa有可能扫不到有些点

差不多这样,AC代码
 

#include<bits/stdc++.h>
using namespace std;int read(){int s=0,fl=1;char w=getchar();while(w>'9'||w<'0'){if(w=='-')fl=-1;w=getchar();}while(w<='9'&&w>='0'){s=s*10+(w^48);w=getchar();}return fl*s;
}
void out(int x){if(x<0)putchar('-'),x=-x;if(x<10)putchar(x+'0');else out(x/10),putchar(x%10+'0');
}
int n,m;
bool inq[100010];
int head[100010],to[100010],ne[100010],w[100010],tot;
int dis[100010],cnt[100010];
queue<int>q;
void add(int x,int y,int v){to[++tot]=y;ne[tot]=head[x];head[x]=tot;w[tot]=v;
}
bool spfa(int s1) {for(int i = 1; i <= n+1; i++) {dis[i] = 2147483647;cnt[i] = 0;inq[i] = false;}dis[s1] = 0;q.push(s1);inq[s1] = true;cnt[s1]++;int x;while(!q.empty()) {x = q.front();q.pop();inq[x] = false;for(int i = head[x]; i; i = ne[i]) {if(dis[to[i]] > dis[x] + w[i]) {dis[to[i]] = dis[x] + w[i];if(!inq[to[i]]) {q.push(to[i]);inq[to[i]] = true;cnt[to[i]]++;if(cnt[to[i]] > n) {return true;}}}}}return false;
}int main(){n=read();m=read();for(int i=1;i<=m;i++){int a,b,c;a=read();b=read();c=read();add(b,a,c);}for(int i=1;i<=n;i++){add(n+1,i,0);}if(spfa(n+1)){cout<<"NO";return 0;}for (int i = 1; i <= n; i++){printf("%d ", dis[i]);}return 0;
}

双倍经验

P1993 小 K 的农场 - 洛谷

国际惯例先骗分,全输出No有85分

来正经做

根据题目,我们能列出三个不等式

还有一个就是相等,移项后可得一个减去另一个=0;也就是这条边的边权为0

其实就很简单了,建图要满足第一个等式,我们只需要两边都乘上-1就能改变不等号的方向。

直接上代码吧,其他都是一样的

#include<bits/stdc++.h>
using namespace std;int read(){int s=0,fl=1;char w=getchar();while(w>'9'||w<'0'){if(w=='-')fl=-1;w=getchar();}while(w<='9'&&w>='0'){s=s*10+(w^48);w=getchar();}return fl*s;
}
void out(int x){if(x<0)putchar('-'),x=-x;if(x<10)putchar(x+'0');else out(x/10),putchar(x%10+'0');
}
int n,m,a,b,c,tot;
int head[1000010],ne[1000010],to[1000010],w[1000010];
int d[1000010],cnt[1000010];
bool inq[1000010];
queue<int>q;
void add(int x,int y,int c){to[++tot]=y;ne[tot]=head[x];head[x]=tot;w[tot]=c;
}
bool spfa(int s){for(int i=1;i<=n+1;i++){d[i]=1e18;inq[i]=false;cnt[i]=0;}d[s]=0;q.push(s);inq[s]=true;cnt[s]++;int x;while(!q.empty()){x=q.front();q.pop();inq[x]=false;for(int i=head[x];i;i=ne[i]){if(d[to[i]]>w[i]+d[x]){d[to[i]]=w[i]+d[x];if(!inq[to[i]]){q.push(to[i]);inq[to[i]]=true;cnt[to[i]]++;if(cnt[to[i]]>n){return true;}}}}}return false;
}
int main(){n=read();m=read();for(int i=1;i<=m;i++){int op=read();if(op==1){a=read();b=read();c=read();add(a,b,-c);}else if(op==2){a=read();b=read();c=read();add(b,a,c);}else{a=read();b=read();add(a,b,0);add(b,a,0);}}for(int i=1;i<=n;i++){add(n+1,i,0);}if(spfa(n+1)){cout<<"No";return 0;}cout<<"Yes";return 0;
}

http://www.dtcms.com/a/339944.html

相关文章:

  • 【自动驾驶】8月 端到端自动驾驶算法论文(arxiv20250819)
  • 决策树1.1
  • 设计模式笔记_行为型_解释器模式
  • 集成电路学习:什么是Thresholding阈值处理
  • PowerBI VS FineBI VS QuickBI实现帕累托分析
  • Go 并发入门:从 goroutine 到 worker pool
  • 用 C++ 构建高性能测试框架:从原型到生产实战指南
  • Python 项目里的数据预处理工作(数据清洗步骤与实战案例详解)
  • 在线客服系统访客表的设计与实现-增加最新消息字段
  • Task01: CAMEL环境配置及第一个Agent
  • Kubernetes Ingress实战:从环境搭建到应用案例
  • C语言基础:(十九)数据在内存中的存储
  • Java线程池参数配置的坑:`corePoolSize=0` + `LinkedBlockingQueue`直接变成串行执行
  • Python爬虫第二课:爬取HTML静态网页之《某某小说》 小说章节和内容完整版
  • 智驾-AEB
  • 羟氯喹通过抑制抗磷脂综合征诱导的绒毛外滋养细胞过度自噬
  • 【模版匹配】基于深度学习
  • 洛谷 P2834 纸币问题 3-普及-
  • 《当 AI 学会 “思考”:大语言模型的逻辑能力进化与隐忧》
  • centos 总有new mail出现原因
  • [论文阅读] 软件工程 - 用户体验 | VR应用的无障碍性困局:基于Meta和Steam商店评论的深度剖析
  • 多幅图片拼接算法系统
  • FIFO通讯速率> 30MB/s,CH346保障FPGA与PC的高速通道稳定高效
  • 当GitHub宕机时,我们如何协作
  • 工业4.0时代,耐达讯自动化Profibus转光纤如何重构HMI通信新标准?“
  • HTML应用指南:利用GET请求获取全国新荣记门店位置信息
  • 【DAB收音机】DAB服务跟随Service Follow功能(三)【FIG 0/24:OE Services】
  • Browser Use + Playwright到AI Agent:Web自动化如何实现质变?
  • C++装饰器模式:从“勇勇”例子到实际应用
  • Day09 Go语言深入学习(1)