图论——负环以及玄学优化

负环的定义

一个图中存在一个环,里面包含的边的边权总和<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;
}

牛客—— 虫洞 Wormholes

思路:

还是求负环,要注意初始化数组。

统计当前每个点的最短路所包含的边数,如果>=n,则存在负环。

添加有虫洞的边时,权值应当为-w。

代码: https://ac.nowcoder.com/acm/contest/view-submission?submissionId=43840156&returnHomeType=1&uid=115850812