[模板]判负环
- 负环大概指边权和为负的环
- 给一个n个点m条边的有向图(如果是无向边加两次有向边应该就好啦),判断是否存在负环
- n,m≤2e5
- QwQ下面一些内容有参考wyhhhhhhhh的博客:用SPFA判断负环的方法及其优化
- Luogu的模板题:【模板】负环
- 如有错误还请多指教
我一开始自己写了个最普通的bfs版的spfa然后光荣的TLE了三个点(一共才五个点)x
之后去百度了一下才知道还有dfs版的orz
个人理解:
平常写的bfs版的应该是可以防止爆栈之类的情况所以用队列做,复杂度O(kn),而这里的k是点进队的平均次数,而直接这样判负环就要判是否有某个点进队次数大于n次,这样子如果存负环最坏情况就很糟糕了(而且是经常这样),然后就有了下面的东西。
dfs版的spfa的判断是在松弛操作时判断这个结点是否第二次出现,如果是的话显然这条路径上就有负环了(如果没有的话自然不会走第二次),写的时候只要多开一个bool数组顺便记录一下某些点是否被访问过就好。
不过直接这样写据说还是会T掉(虽然我还没试_(:з」∠)_),然后下面的东西就是在wyhhhhhhhh大神的那篇博客里学来的优化技巧(趴)
就是说既然只需要判断是否有负环,那就直接找边权和为负的回路,一开始把dis数组(到点的距离)全部设为0然后跑,这样一开始只会扩展到负权的边。
之后我们再以每个节点开始跑一边spfa,如果路径上某个点第二次松弛那就直接跳出来啦。
注意记得回溯
边集数组记得开两倍
搜的时候xjb剪剪枝
最后还是贴一下代码(以后还是不折叠好了)x
#include<cstdio> #include<cstring> typedef long long lint; const int N=200005; struct edge{lint to,weight,next;}edges[N<<1]; lint n,m,t,cnt; lint dis[N],head[N]; bool flag,vis[N]; inline lint read() { lint s=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){s=s*10+c-'0';c=getchar();} return s*f; } inline void addEdge(lint u,lint v,lint w) { edges[++cnt]=(edge){v,w,head[u]}; head[u]=cnt; } inline void spfa(int cur) { if(flag)return; vis[cur]=1; for(int i=head[cur];i;i=edges[i].next) { if(flag)return; if(dis[cur]+edges[i].weight<dis[edges[i].to]) { dis[edges[i].to]=dis[cur]+edges[i].weight; if(vis[edges[i].to]) { flag=1;return; }else spfa(edges[i].to); } }vis[cur]=0; } int main() { //freopen("input.in","r",stdin); t=read(); while(t--) { memset(head,0,sizeof(head)); memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); cnt=flag=0; n=read();m=read(); for(int i=1;i<=m;i++) { int u,v,w; u=read();v=read();w=read(); addEdge(u,v,w); if(w>=0)addEdge(v,u,w); } for(int i=1;i<=n;i++) { spfa(i); if(flag)break; } printf(flag?"YE5":"N0"); printf("\n"); } return 0; }