• 找负环


    WARNING

    朴素SPFA的时间复杂度为(ke)(k)在极端情况下会趋近于(n),接近于Bellman-Ford算法。这种极端情况并不罕见,在近几年的竞赛中这种恶意数据很多,如NOI2018的T1,SPFA死掉了。

    所以,求最短路时,如果图没有负边权的话,建议使用队列优化的Dijkstra。

    BFS_SPFA

    之前讲朴素SPFA时谈到过,判断负环的方式是记录每个点入队的次数,如果大于N就说明有负环,但这种方法会很慢,考虑图就是一个大环(负),你需要绕n圈才能使得有点入队N次,时间复杂度就是(NE)了。

    考虑另一种方法:对于一个不存在负环的图,从起点到任意一个点最短距离经过的点最多只有 n 个,这样的话,用 cnt[i] 表示从起点(假设就是1)到i的最短距离包含点的个数,初始化cnt[1]=1,那么当我们能够用点u松弛点v时,松弛时同时更新cnt[v]=cnt[u]+1,若发现此时cnt[v]>n,那么就存在负环。

    可以这样理解,cnt[i]代表当前结点i在当前路径上的深度,因此每走一步深度就要加1,一旦超过N,就发现了负环。考虑上面的例子,图是一个大环(负),你只需要绕1圈就可以发现负环,效率很高。

    //朴素BFS的SPFA,使用了STL的queue,AC
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue> 
    using namespace std;
    const int maxn=2020, maxm=3020, INF=0x3f3f3f3f;
    struct edge{ int t, w, nxt; }E[maxm<<2];
    int T, N, M, h[maxn], tot;
    void add(int u, int v, int w){ E[++tot].t=v; E[tot].w=w; E[tot].nxt=h[u]; h[u]=tot; }
    
    int read()
    {
        int s=0, w=1; char ch=getchar();
        while(ch<'0' || ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
        while(ch>='0'&& ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
        return s*w; 
    }
    
    int inq[maxn], cnt[maxn], dis[maxn];				//inq标记点在不在queue中,cnt统计点入队的次数 
    
    bool SPFA(int s)
    {
        queue<int> q;
        for(int i=1; i<=N; i++) dis[i]=(i==s)?0:INF;
        memset(inq, 0, sizeof(inq));
        memset(cnt, 0, sizeof(cnt));
        q.push(s), inq[s]=1;
        while(!q.empty())
        {
            int x=q.front(); q.pop(); inq[x]=false;
            for(int p=h[x]; p; p=E[p].nxt)
            {
                int to=E[p].t, w=E[p].w;
                if(dis[x]+w<dis[to])
                {
                    dis[to]=dis[x]+w;
                    //判负环方法1: 								593ms 
                    cnt[to]=cnt[x]+1; 
                    if(cnt[to]>N)	return true;
                    //判负环方法2:if(++cnt[to]>=N)	return true;	786ms
                    if(!inq[to])
                    { 
                        q.push(to);
                        inq[to]=1;
                    }
                }
            }
        }
        return false;
    } 
    
    int main()
    {
        T=read();
        while(T--)
        {
            memset(h, 0, sizeof(h));					//清空链式前向星 
            tot=0;
            N=read(), M=read();							//读入图的数据 
            for(int i=1, a, b, w; i<=M; i++)
            {
                a=read(), b=read(), w=read();
                if(w<0)		add(a, b, w);				//看清题目要求 
                else		add(a, b, w), add(b, a, w);
            }	
            if(SPFA(1))		printf("YE5
    ");			//SPFA判环
            else			printf("N0
    ");				//注意输出的是YE5和N0,不是YES和NO 
        }
        return 0;
    }
    

    //BFS的SPFA+SLF优化,使用了STL的deque,效率更高,但9号点莫名TLE,91分,目前原因不明。 
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue> 
    using namespace std;
    const int maxn=2020, maxm=3020, INF=0x3f3f3f3f;
    struct edge{ int t, w, nxt; }E[maxm<<1];
    int T, N, M, h[maxn], tot;
    void add(int u, int v, int w){ E[++tot].t=v; E[tot].w=w; E[tot].nxt=h[u]; h[u]=tot; }
    
    int read()
    {
        int s=0, w=1; char ch=getchar();
        while(ch<'0' || ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
        while(ch>='0'&& ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
        return s*w; 
    }
    
    int inq[maxn], cnt[maxn], dis[maxn];				//inq标记点在不在queue中,cnt统计点入队的次数 
    
    bool SPFA(int s)
    {
        for(int i=1; i<=N; i++)   dis[i]=(i==s) ? 0 : INF;
        deque<int> Q;									//Q为双端队列 
        memset(inq, 0, sizeof(inq));
        memset(cnt, 0, sizeof(cnt));
        Q.push_back(s), inq[s]=1, cnt[s]=1;
        while(!Q.empty())
        {
            int x=Q.front(); Q.pop_front(); inq[x]=false; 
            for(int p=h[x]; p; p=E[p].nxt){
                int to=E[p].t, w=E[p].w;
                if(dis[to]>dis[x]+w)
                {
                    dis[to]=dis[x]+w;
                    cnt[to]=cnt[x]+1;
                    if(cnt[to]>N)	return true;
                    if(!inq[to])
                    {
                        inq[to]=1;
                        if(Q.empty() || dis[to]>dis[Q.front()])   	Q.push_back(to);
                        else                                        Q.push_front(to);
                    }
                }
            }
        }
        return false;
    }
    
    int main()
    {
        T=read();
        while(T--)
        {
            memset(h, 0, sizeof(h));					//清空链式前向星 
            tot=0;
            N=read(), M=read();							//读入图的数据 
            for(int i=1, a, b, w; i<=M; i++)
            {
                a=read(), b=read(), w=read();
                if(w<0)		add(a, b, w);				//看清题目要求 
                else		add(a, b, w), add(b, a, w);
            }	
            if(SPFA(1))		printf("YE5
    ");			//SPFA判环
            else			printf("N0
    ");				//注意输出的是YE5和N0,不是YES和NO 
        }
        return 0;
    }
    

    DFS_SPFA

    基于 dfs 版的 SPFA 相当于是把“先进先出”的队列换成了“先进后出”的栈,每次都以刚刚松弛过的点来松弛其他的点,如果能够松弛点 x 并且 x 还在栈中,那图中就有负环。

    一般来说的话,若存在负环,那么 dfs 会比 bfs 快,但是如果不存在负环,dfs 可能会严重影响求最短路的效率,要谨慎使用

    //DFS的SPFA,效率更高,但9号点莫名TLE,91分,目前原因不明,好多飞快的AC代码9号点都是打表过的。  
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=2020, maxm=3020, INF=0x3f3f3f3f;
    struct edge{ int t, w, nxt; }E[maxm<<1];
    int T, N, M, h[maxn], tot; 
    void add(int u, int v, int w){ E[++tot].t=v; E[tot].w=w; E[tot].nxt=h[u]; h[u]=tot; }
    
    int read()
    {
        int s=0, w=1; char ch=getchar();
        while(ch<'0' || ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
        while(ch>='0'&& ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
        return s*w; 
    }
    
    int instack[maxn], dis[maxn], flag;					//instack标记点是否在当前的递归栈中,flags为是否有负环的标记
    
    void SPFA(int x)
    {
    	instack[x]=1;
    	for(int p=h[x]; p; p=E[p].nxt)
    	{
    		if(flag)	return;							//已经找到负环,停止继续搜索 
    		int to=E[p].t, w=E[p].w;
    		if(dis[to]>dis[x]+w)
    		{
    			if(instack[to])							//to能被松弛且在递归栈中,图中有负环 
    			{
    				flag=true;
    				return;
    			}
    			dis[to]=dis[x]+w;
    			SPFA(to);
    		}
    	}
    	instack[x]=0;
    }
    
    int main()
    {
        T=read();
        while(T--)
        {
            memset(h, 0, sizeof(h));					//清空链式前向星 
            tot=0;
            flag=0;
            memset(instack, 0, sizeof(instack));
            memset(dis, 0x3f,  sizeof(dis));
    		dis[1]=0;
            N=read(), M=read();							//读入图的数据 
            for(int i=1, a, b, w; i<=M; i++)
            {
                a=read(), b=read(), w=read();
                if(w<0)		add(a, b, w);				//看清题目要求 
                else		add(a, b, w), add(b, a, w);
            }
            SPFA(1);
            if(flag)		printf("YE5
    ");			//SPFA判环
            else			printf("N0
    ");				//注意输出的是YE5和N0,不是YES和NO 
        }
        return 0;
    }
    
  • 相关阅读:
    11. 盛最多水的容器
    820. 单词的压缩编码
    72. 编辑距离
    10041-回文数
    第六章-希尔排序和堆排序
    第六章-快速排序、合并排序
    第六章 排序-冒泡,选择,插入排序
    第四章-二叉树的运用-AVL平衡二叉树
    第四章-二叉树的运用-排序二叉树的代码
    第四章-二叉树的代码 、二叉树前序中序后序遍历、及运用
  • 原文地址:https://www.cnblogs.com/lfyzoi/p/10640695.html
Copyright © 2020-2023  润新知