[OI] 关于最小环和负权环
最小环-Floyed
<
关于Floyed能用于求解最小环的证明
对于一个环,我们总可以在环上找到任意三个点
我们设从
显然,对于有向图,我们也有
这恰好就是Floyed的写法,而上述式子便是松弛方程.
但是有一点不太一样,在进行遍历时,一个点
另一点需要注意的是,这里可能代码里可能会出现
代码实现
int Floyd(int n){
int mc=inf;
for(int k=0;k<=n;++k){
for(int i=0;i<k;++i){
for(int j=i+1;j<k;++j){
mc=min(mc,e[i][k]+e[k][j]+dis[i][j]);
}
}
for(int i=0; i<=n;++i){
for(int j=0;j<=n;++j){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
return mc;
}
下面给出了存图和初始化的示例
for(int i=0;i<=n;++i){
for(int j=0;j<=n;++j){
if(i==j){
w[i][j]=dis[i][j]=0;
}
else{
w[i][j]=dis[i][j]=inf;
}
}
}
for(int i=0;i<m;++i){
cin>>u>>v>>y;
w[u][v]=dis[u][v]=y;
w[v][u]=dis[v][u]=y;
}
负权环-SPFA
<
关于SPFA
SPFA 我觉得和Dijkstra还挺像的 ,但是我背不过 ,所以简单写一下
很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。
那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。
SPFA的简单步骤大致如下 (
- 需要的变量 : 存图变量,
<用于存储起点到各点的距离>, <用于记录节点是否在队列里> 和一个队列. - 初始化 :
数组需要全部初始化为一个很大的值 (大于最大边权,小于1,073,741,823,否则两个数相加会超 范围变成负的,假如你用 ,推荐使用“ ”将其初始化为 ) ,如果需要重复使用,建议初始化 . - 起点的
设为0, 设为1,然后放入队列 . - 随后,每次从队列中拿出一个值,将其
修改为 0,对其所有的边进行松弛操作,如果成功松弛的边不在队列里 ( 就是在这里用的),那么入队, 修改为1.
这里对SPFA和SPFA能用于判断负环的正确性不再说明.
SPFA判断负环的方法
假如我们有
所以我们需要想办法记录当前最短路经过的节点数
我们只需要加上这么两句 (假设
cont[j]=cont[i]+1;
if(cont[j]=>n) return false;
其中
代码实现
bool spfa(int s){
for(int i=1;i<=n;++i){
dis[i]=1000000000;
vis[i]=false;
cont[i]=0;
}
dis[s]=0;
vis[s]=true;
q.push(s);
cont[s]++; //这别忘了
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(edge ed:e[u]){
int to=ed.to,w=ed.w;
if(dis[to]>dis[u]+w){
dis[to]=dis[u]+w;
if(!vis[to]){
q.push(to);
cont[to]++; //注意,这一段一定要在if里面
if(cont[to]>=n){
return true; //有负权环
}
vis[to]=true;
}
}
}
}
return false;
}
例题 P2850 [USACO06DEC] Wormholes G
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to,w;
};
vector<edge> e[501];
int dis[501],cont[501];
bool vis[501];
queue<int> q;
int f,n,m,w;
bool spfa(int s){
for(int i=1;i<=n;++i){
dis[i]=1000000000;
vis[i]=false;
cont[i]=0;
}
dis[s]=0;
vis[s]=true;
q.push(s);
cont[s]++;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(edge ed:e[u]){
int to=ed.to,w=ed.w;
if(dis[to]>dis[u]+w){
dis[to]=dis[u]+w;
if(!vis[to]){
q.push(to);
cont[to]++;
if(cont[to]>=n){
return false;
}
vis[to]=true;
}
}
}
}
return true;
}
int main(){
cin>>f;
for(int k=1;k<=f;++k){
for(int i=1;i<=500;++i){
e[i].clear();
}
cin>>n>>m>>w;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
e[x].push_back(edge{y,z});
e[y].push_back(edge{x,z});
}
for(int i=1;i<=w;++i){
int x,y,z;
cin>>x>>y>>z;
e[x].push_back(edge{y,-1*z});
}
bool flag=false;
for(int i=1;i<=n;++i){
if(spfa(i)==false){
cout<<"YES"<<endl;
flag=true;
break;
}
}
if(flag==false){
cout<<"NO"<<endl;
}
}
}
负权环-BellmanFord
<
当然,SPFA的代码不是很好背,但是我们可以用伟大的Bellman-Ford,这两种算法起码对菊花图来说一样快,思路十分简单:遍历
代码实现
bool bf(){
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
for(int k=1;k<=n;++k){
for(edge i:e){ //把所有边扔到结构体vector e里
if(dis[i.to]>dis[i.start]+i.w){
dis[i.to]=dis[i.start]+i.w;
}
}
}
for(edge i:e){
if(dis[i.to]>dis[i.start]+i.w){
return true; //有负权环
}
}
return false;
}
例题(见上SPFA)
#include<bits/stdc++.h>
using namespace std;
struct edge{
int start,to,w;
};
vector<edge> e;
int dis[501];
int f,n,m,w;
bool bf(){
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
for(int k=1;k<=n;++k){
for(edge i:e){
if(dis[i.to]>dis[i.start]+i.w){
dis[i.to]=dis[i.start]+i.w;
}
}
}
for(edge i:e){
if(dis[i.to]>dis[i.start]+i.w){
return true;
}
}
return false;
}
int main(){
cin>>f;
for(int k=1;k<=f;++k){
e.clear();
cin>>n>>m>>w;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
e.push_back(edge{x,y,z});
e.push_back(edge{y,x,z});
}
for(int i=1;i<=w;++i){
int x,y,z;
cin>>x>>y>>z;
e.push_back(edge{x,y,-z});
}
if(bf()==false){
cout<<"NO"<<endl;
}
else{
cout<<"YES"<<endl;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!