针对于spfa判环的二三理解

SPFA

一、对于spfa本身的理解

在判环之前,要先简单了解一下SPFA算法

(以下的内容部分摘自 https://blog.csdn.net/forever_dreams/article/details/81161527 和 百度

  1. SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。

  2. 松弛操作,它的原理是著名的定理:“三角形两边之和大于第三边”,也可以说是三角不等式。若用 u 来松弛 v,就是判断是否 dis[ v ] > dis[ u ] + w[ u , v ],如果该式成立则将 dis[ v ] 更新到 dis[ u ] + w[ u , v ],否则不变。

  3. SPFA相当于是Bellman-Ford算法的一个优化版本,在Bellman-Ford算法中,很多松弛操作其实都是没有必要的,例如:对于一条从 x 到 y 的边,如果连 x 都还没被松弛,那 y 肯定也还不能被 x 松弛,为了避免“用一个还没有被松弛的点去松弛另外的点”的情况,我们用一个队列来存储已经被松弛过的点,然后用队列里的点去松弛其他点,这就是SPFA算法的基本思想

  4. 下面是具体的操作过程:
    我们先把起点加入队列中,每次取出队首元素 u,然后尝试松弛点 u 能到达的点 v,若点 v 能被松弛且点 v 此时还没有被加进队列里,那么我们就把点 v 入队,不断地这样操作,直到队列为空。这样的话,只要最短路存在,那么我们就一定能求出最短路,与dijkstra不同的是spfa是基于遍历边的思想,只要可以被更新,可能一条边会被遍历很多次,从而一个点也可能会进队和出队很多次。

但是,SPFA有时会被恶意数据卡掉,为了避免最坏情况的出现,在正权图上应使用效率更高的Dijkstra算法。

二、spfa判负环

首先理解一下,负环:一个权值和为负数的环
在这里插入图片描述
<思路>
1.上图是一个有向的负权图,由第一部分我们可以知道,spfa是基于遍历边的思想,也就是说只要这一条边可以被更新,那么这条边上的点就会进入队列。

2.其次,要跑最短路,一定是找到最小的边权值,然后就不断更新,直到找到最小的答案。但是,在一个负权图中,只要跑到负权边,那么这个答案就会被不断的更新,也就是说,负权边会被不断地更新,负权边上的点就会不断的进入队列。

3.对于一个正常的正权图而言,每个点最多被更新n-1次(最坏情况,每个点都可以把它更新)
,如果说是,有一个点被更新大于等于n次的话,也就是说,这里有负环。

<代码>
题目:P3385 【模板】负环
【bfs版】

#include<bits/stdc++.h>
using namespace std;
const int nn=2010;
const int mm=3010;
int t,n,m;
int num=0;
int d[nn],vis[nn],v[nn],r[nn];

int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

struct edge
{
    int fr,to,ver,nex;
}e[2*mm];
int last[nn];

void add(int x,int y,int z)
{
    e[++num].fr=x; e[num].to=y; e[num].ver=z;
    e[num].nex=last[x]; last[x]=num;
}

bool spfa(int xx)
{
    queue<int> q;
    memset(d,0x3f3f3f3f,sizeof(d));
    memset(vis,0,sizeof(vis));
    d[xx]=0,vis[xx]=1;
    q.push(xx);
    while(q.size())
    {
        int x=q.front(); q.pop();
        vis[x]=0;
        for(int i=last[x];i;i=e[i].nex)
        {
            int y=e[i].to;
            int z=e[i].ver;
            v[y]=1;
            if(d[y]>d[x]+e[i].ver)
            {
                d[y]=d[x]+e[i].ver;
                r[y]=r[x]+1;//r[i]数组保存的是i节点被更新的次数
                if(!vis[y])
                    vis[y]=1,q.push(y);
            }
            if(r[y]>=n)		return true;
        }
    }
    return false;
}

int main()
{
    t=read();
    while(t)
    {
        t--;
        n=read(); m=read();
        num=0;
        memset(e,0,sizeof(e));
        memset(last,0,sizeof(last));
        memset(v,0,sizeof(v));
        memset(r,0,sizeof(r));
        for(int i=1;i<=m;++i)
        {
            int a=read();
            int b=read();
            int c=read();
            if(c<0)		add(a,b,c);
            else
            {
                add(a,b,c);
                add(b,a,c);
            }
        }
        for(int i=1;i<=n;++i)
        {
            if(v[i])	continue;
            v[i]=1;
            if(spfa(i))
            {
                cout<<"YE5"<<endl;
                break;
            }
            else
            {
                cout<<"N0"<<endl;
                break;
            }
        }
    }
    return 0;
}

听说还有【dfs版】,但是笔者没有打过
【dfs版】详见于:(https://blog.csdn.net/forever_dreams/article/details/81161527 )

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

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

三、spfa判正环

题目:P1938 [USACO09NOV]找工就业Job Hunt
题目:P1984 [差分约束]奖学金(差分约束+spfa跑最长路+spfa判正环)
题目:P1227关系运算图(差分约束+spfa跑最长路+spfa判正环)
正环:顾名思义,是一个环的边权值和为正数。
在这里插入图片描述
思路
思路基本和上面的是一样的,就是三角不等式要换一下,当 dis[ v ] < dis[ u ] + w[ u , v ]的时候更新,然后和上面一样,统计更新过的次数,判断。【正解的简洁明了??】
代码

#include<bits/stdc++.h>
using namespace std;
const int nn=230;
const int mm=500;
int D,p,c,f,s;
int num=0;
int d[nn],vis[nn],r[nn];
int maxx=-100;

queue<int> q;

inline int read()
{
    int 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*10+ch-'0'; ch=getchar();}
    return f*x;
}

struct edge
{
    int fr,to,ver,nex;
}e[mm];
int last[nn];

void add(int x,int y,int z)
{
    e[++num].fr=x; e[num].to=y; e[num].ver=z;
    e[num].nex=last[x]; last[x]=num;
}

bool spfa(int xx)
{
    memset(vis,0,sizeof(vis));
    memset(d,0xcf,sizeof(d));
    d[xx]=D;
    q.push(xx);
    vis[xx]=1;
    while(q.size())
    {
        int x=q.front(); q.pop();
        vis[x]=0;
        for(int i=last[x];i;i=e[i].nex)
        {
            int y=e[i].to,z=e[i].ver;
            if(d[x]+z>d[y])/*这里这里,要换一下三角不等式*/
             {
                d[y]=d[x]+z;
                maxx=max(maxx,d[y]);
                if(!vis[y])
                    q.push(y),vis[y]=1;
                r[y]++;
                if(r[y]>c)	return true;
            }
        }
    }
    return false;
}

void in()//输入和建图
{
    D=read(); p=read(); c=read(); f=read(); s=read();
    for(int i=1;i<=p;++i)
    {
        int a=read();
        int b=read();
        add(a,b,D);
    }
    for(int i=1;i<=f;++i)
    {
        int a=read();
        int b=read();
        int c=read();
        add(a,b,-c+D);
    }
}

int main()
{
    in();
    if(spfa(s))	cout<<-1<<endl;
    else	cout<<maxx<<endl;
    return 0;
} 

习题:

  1. 狡猾的商人:考虑一下如何建图,如何建边, 建完之后我们可以用SPFA跑环吗?跑什么环?正环还是负环?

完……

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值