AcWing 算法提高课 SPFA求负环
1、方法:
一般推荐方法(2),最差时间复杂度较低
模板:

int n,m,w; const int N=510; vector<int> adj[N]; vector<int> cost[N]; int dis[N]; bool st[N]; int cnt[N]; bool SPFA() { memset(dis,0,sizeof(dis)); memset(st,0,sizeof(st)); memset(cnt,0,sizeof(cnt)); queue<int> que; fore(i,1,n) { st[i]=true; que.push(i); } while(que.size()) { int u=que.front(); que.pop(); st[u]=false; auto &nxts=adj[u]; auto &costs=cost[u]; for(int i=0;i<nxts.size();i++) { int nxt=nxts[i]; int w=costs[i]; if(dis[nxt]>dis[u]+w) { dis[nxt]=dis[u]+w; cnt[nxt]=cnt[u]+1; if(cnt[nxt]>=n) return true; if(!st[nxt]) { st[nxt]=true; que.push(nxt); } } } } return false; } void YD() { cin>>n>>m>>w; fore(i,1,n) { adj[i].clear(); cost[i].clear(); } int a,b,c; while(m--) { cin>>a>>b>>c; adj[a].push_back(b); cost[a].push_back(c); adj[b].push_back(a); cost[b].push_back(c); } while(w--) { cin>>a>>b>>c; adj[a].push_back(b); cost[a].push_back(-c); } if(SPFA()) cout<<"YES"<<endl; else cout<<"NO"<<endl; }
注意,在初始化时将全部dis初始化为0(可以证明,不初始化也不影响,因为若存在负环则会不断循环减小路径长度);
相当于虚拟源点向全部点都有一条权重为0的边,故初始还需要将全部点加入队列中。
cnt数组用来统计每个点的最短路中所包含的边数。
2、求是否有正环
按1、中方法,改变不等号求正环即可(SPFA求最长路)
一般判断负环的时间复杂度是O(nm)可能会超时
trick:当SPFA效率较低时,可以假设存在负环,
即SPFA算法所有点入队了2n-3n次就视为存在负环。
例题:
https://www.acwing.com/problem/content/description/1167/
代码:

#include<bits/stdc++.h> #define fore(x,y,z) for(LL x=(y);x<=(z);x++) #define forn(x,y,z) for(LL x=(y);x<(z);x++) #define rofe(x,y,z) for(LL x=(y);x>=(z);x--) #define rofn(x,y,z) for(LL x=(y);x>(z);x--) #define all(x) (x).begin(),(x).end() #define fi first #define se second using namespace std; typedef long long LL; typedef pair<int,int> PII; typedef pair<LL,LL> PLL; vector<int> adj[720]; vector<int> cost[720]; const int N=720; bool st[720]; int cnt[720]; double dis[720]; bool Check(double mid) { memset(st,0,sizeof(st)); memset(cnt,0,sizeof(cnt)); int tmp=0; queue<int> que; fore(i,0,N-1) que.push(i),st[i]=true; while(que.size()) { int u=que.front(); que.pop(); st[u]=false; for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(dis[nxt]<dis[u]+c-mid) { dis[nxt]=dis[u]+c-mid; cnt[nxt]=cnt[u]+1; tmp++; if(tmp>5000) return true; if(cnt[nxt]>=N) return true; if(!st[nxt]) { st[nxt]=true; que.push(nxt); } } } } return false; } int n; void YD() { string str; fore(i,0,N) { adj[i].clear(); cost[i].clear(); } while (n -- ){ cin>>str; if(str.size()>=2) { int num1=(str[0]-'a')*26+str[1]-'a'; int num2=(str[str.size()-2]-'a')*26+str[str.size()-1]-'a'; adj[num1].push_back(num2); cost[num1].push_back(str.size()); } } double l=0,r=1000; while(r-l>1e-4) { double mid=(r+l)/2; if(Check(mid)) { l=mid; } else { r=mid; } } if(l-0<1e-3) { cout<<"No solution"<<endl; } else cout<<l<<endl; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); while (cin>>n,n) { YD(); } return 0; }
tmp统计修改距离次数

#include<bits/stdc++.h> #define fore(x,y,z) for(LL x=(y);x<=(z);x++) #define forn(x,y,z) for(LL x=(y);x<(z);x++) #define rofe(x,y,z) for(LL x=(y);x>=(z);x--) #define rofn(x,y,z) for(LL x=(y);x>(z);x--) #define all(x) (x).begin(),(x).end() #define fi first #define se second using namespace std; typedef long long LL; typedef pair<int,int> PII; typedef pair<LL,LL> PLL; vector<int> adj[720]; vector<int> cost[720]; const int N=720; bool st[720]; int cnt[720]; double dis[720]; bool Check(double mid) { memset(st,0,sizeof(st)); memset(cnt,0,sizeof(cnt)); int tmp=0; queue<int> que; fore(i,0,N-1) que.push(i),st[i]=true; while(que.size()) { int u=que.front(); que.pop(); st[u]=false; for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(dis[nxt]<dis[u]+c-mid) { dis[nxt]=dis[u]+c-mid; cnt[nxt]=cnt[u]+1; if(cnt[nxt]>=N) return true; if(!st[nxt]) { tmp++; if(tmp>3000) return true; st[nxt]=true; que.push(nxt); } } } } return false; } int n; void YD() { string str; fore(i,0,N) { adj[i].clear(); cost[i].clear(); } while (n -- ){ cin>>str; if(str.size()>=2) { int num1=(str[0]-'a')*26+str[1]-'a'; int num2=(str[str.size()-2]-'a')*26+str[str.size()-1]-'a'; adj[num1].push_back(num2); cost[num1].push_back(str.size()); } } double l=0,r=1000; while(r-l>1e-4) { double mid=(r+l)/2; if(Check(mid)) { l=mid; } else { r=mid; } } if(l-0<1e-3) { cout<<"No solution"<<endl; } else cout<<l<<endl; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); while (cin>>n,n) { YD(); } return 0; }
注意在Check函数中,tmp统计总共的入队次数,如果过大则直接判断存在负环(正环)
trick2:SPFA的队列改成栈,然后用1、中方法判断
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人