图论——负环以及玄学优化
负环的定义
一个图中存在一个环,里面包含的边的边权总和<0 。在含有负环的图,是无法求出最短路的,因为只要是重复走这个环,最短路就会不断缩小。
负环的求法
基于Bellman-Ford算法
若n次迭代,算法仍旧未结束,则图中存在负环,反正咋不存在负环。
基于SPFA算法。
1.统计每个点入队的次数,如果某个点入队n次(也可以大于),说明存在负环。
2.统计当前每个点的最短路所包含的边数,如果某点的最短路包含的边数>=n,说明存在负环。
关于算法2,进阶指南的解释如下:
根据抽屉原理,如果某点的最短路包含的边数>=n,那么这条最短路必然重复经过某个点p,即最短路上存在一个环使得环上各点都能更新下一点的dis值,最短路的长度越来越短,无法结束算法。
玄学优化
1.当所有点的入队次数超过2n时,我们就认为图中很大可能存在负环。
2.可以把队列换为堆。
3.把SPFA基于BFS改为基于DFS
题目:
题意:
给定一个无向图,问是否存在负环。
思路:
用SPFA判断每个点入队的次数,要注意有多组测试数据,记得清空数组。
代码:
#pragma GCC optimize(2) #include<bits/stdc++.h> typedef long long ll; using namespace std; typedef pair<int,int>PII; inline ll read(){ ll x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if (ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ x = (x<<1) + (x<<3) + (ch^48); ch = getchar(); } return x * f; } const int maxn=1e6+7; int h[maxn],e[maxn],w[maxn],ne[maxn],idx; int n,m,r,t; int d[maxn],cnt[maxn]; bool st[maxn]; void add(int a,int b,int c){ e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } int spfa(){ memset(d,0x3f, sizeof d); memset(cnt, 0, sizeof cnt); memset(st, 0, sizeof st); queue<int>q; q.push(1); st[1]=1;cnt[1]++;d[1]=0; while(!q.empty()){ int t=q.front(); q.pop(); st[t]=0; for(int i=h[t];~i;i=ne[i]){ int j=e[i]; if(d[j]>d[t]+w[i]){ d[j]=d[t]+w[i]; cnt[j]=cnt[t]+1; if(cnt[j]>=n) return 1; if(!st[j]){ q.push(j); st[j]=1; } } } } return 0; } int main(){ int T=read(); while(T--){ n=read();m=read(); memset(h,-1,sizeof h); idx=0; while(m--){ int u,v,w; u=read();v=read();w=read(); if(w>=0) add(u,v,w),add(v,u,w); else add(u,v,w); } int tmp=spfa(); if(tmp) puts("YES"); else puts("NO"); } return 0; }
思路:
还是求负环,要注意初始化数组。
统计当前每个点的最短路所包含的边数,如果>=n,则存在负环。
添加有虫洞的边时,权值应当为-w。