[OI] 关于最小环和负权环

最小环-Floyed

< O(N3) (N为节点数)>

关于Floyed能用于求解最小环的证明

对于一个环,我们总可以在环上找到任意三个点 i,j,k . 这三个点会把一个环分成三部分

我们设从 xy 的最短距离为 dis[i][j],那么环的长度可以由这三部分的长度相加,即

l=dis[i][j]+w[i][k]+w[j][k]

显然,对于有向图,我们也有

l=min(dis[j][i]+w[i][k]+w[k][j],dis[i][j]+w[j][k]+w[k][i])

这恰好就是Floyed的写法,而上述式子便是松弛方程.

但是有一点不太一样,在进行遍历时,一个点 k 可以进行松弛的点只能比他本身小,否则 dis[i][j] 将不是一个确定的值,且对于有向图来说,为了不重复遍历,其中一个点的值不应比另一个值要小,即假如有 i=2,j=3 那么接下来的遍历不应有 i=3,j=2 .很显然,假如存在最小环,这样做纯粹在浪费时间.

另一点需要注意的是,这里可能代码里可能会出现 inf+inf+inf 的情况,所以 inf 的取值不宜太大.

代码实现
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

< Θ(kn,mn) (N为节点数,k为很小的常数,M为边数)>

关于SPFA

SPFA 我觉得和Dijkstra还挺像的 ,但是我背不过 ,所以简单写一下

很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。

那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。

SPFA的简单步骤大致如下 (Node 表示点的数量):

  1. 需要的变量 : 存图变量,dis[Node] <用于存储起点到各点的距离>,vis[Node] <用于记录节点是否在队列里> 和一个队列.
  2. 初始化 : dis 数组需要全部初始化为一个很大的值 (大于最大边权,小于1,073,741,823,否则两个数相加会超 int 范围变成负的,假如你用 memset ,推荐使用“0x3f”将其初始化为 0x3f3f3f3f) ,如果需要重复使用,建议初始化 vis .
  3. 起点的 dis 设为0, vis设为1,然后放入队列 .
  4. 随后,每次从队列中拿出一个值,将其 vis 修改为 0,对其所有的边进行松弛操作,如果成功松弛的边不在队列里 (vis 就是在这里用的),那么入队,vis 修改为1.

这里对SPFA和SPFA能用于判断负环的正确性不再说明.

SPFA判断负环的方法

假如我们有 n 个点,那么显然最短路经过的点没办法超过 n 个,假如超过 n 个了说明一定重复走了,所以存在负环

所以我们需要想办法记录当前最短路经过的节点数

我们只需要加上这么两句 (假设 i 刚刚松弛了 j):

cont[j]=cont[i]+1;
if(cont[j]=>n) return false;

其中 cont 是我们新开的数组,初始化为 0.

代码实现
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

< O(mn) (N为节点数,M为边数)>

当然,SPFA的代码不是很好背,但是我们可以用伟大的Bellman-Ford,这两种算法起码对菊花图来说一样快,思路十分简单:遍历 n1 次每条边并松弛,如果还能松弛那么就有负权环 .

代码实现
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;
		}
	}
}
posted @   HaneDaniko  阅读(50)  评论(0编辑  收藏  举报
编辑推荐:
· 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框架的用法!
点击右上角即可分享
微信分享提示