首页 > 其他分享 >SPFA 判负环

SPFA 判负环

时间:2024-06-09 23:22:38浏览次数:15  
标签:cur vis text 负环 判负 SPFA dp

SPFA 判负环

大家好,我是Weekoder!

今天我要讲的是接上一次 SPFA 最短路算法衍生而来的一个应用——判断图中是否存在负环。我将介绍两种判负环的方法,都基于 SPFA。

负环的概念

负环是什么?

负环就是在图中的一个环,其边权之和为负数。如下图:

可以看到,这个环的权值和是 \((-2)+(-1)+1=-2\),为负数,则这是一个负环。哪怕其中有正权值,只要权值和小于 \(0\),这个环即是一个负环。

如果一个图有负环,则该图没有最短路,因为可以在负环中无限绕圈,不断减少路径长度,一直到 \(-\infty\)。

BFS-SPFA 判负环

今天介绍的第一种方法是基于 BFS 的搜索方式的判定方法。在 SPFA 模板的基础上,只修改了一点点,非常容易。它的原理是:维护一个 \(dp\) 数组用于计数,\(dp_i\) 表示点 \(i\) 的最短路所经过的边数。我们知道一个点的最短路最多会经过其他 \(n-1\) 个点。经过多少点,就经过多少边,则我们有:\(dp_i\le n-1\)。我们不会经过任何一个重复的点,除非有负环让这个点继续走下去。那么当上一个式子 \(dp_i\le n-1\) 反过来,就变成了 \(dp_i>n-1\),即 \(dp_i\ge n\),也就是重复走了一个点,有负环。于是我们可以状态转移,对于一条边 \(cut\to nxt\),我们的状态转移方程是:\(dp_{nxt}=dp_{cur}+1\)。然后我们再判断 \(dp_i\ge n\),如果成立则有负环,最后返回没有负环。虽然讲了一大堆,但是代码却几乎没有修改。

参考题目:P3385

\(\text{Code:}\)

bool spfa(int s) {
    queue<int> q;
    memset(dis, 0x3f, sizeof dis);
    memset(vis, 0, sizeof vis);
    memset(dp, 0, sizeof dp); // 记得初始化
    dis[s] = 0;
    vis[s] = 1;
    q.push(s);
    while (!q.empty()) {
        int cur = q.front(); q.pop();
        vis[cur] = 0;
        for (auto nxt : nbr[cur]) {
            int to = nxt.to, w = nxt.w;
            if (dis[to] > dis[cur] + w) {
                dis[to] = dis[cur] + w;
                dp[to] = dp[cur] + 1; // 状态转移方程
                if (dp[to] >= n) return 1; // 如果存在负环,则返回1
                if (!vis[to]) {
                    vis[to] = 1;
                    q.push(to);
                }
            }
        }
    }
    return 0;
}

DFS-SPFA 判负环

这个方法是我在做 P3199 最小圈的时候,因为不知道为什么 \(\color{#052242}\texttt{TLE}\) 而去网上查到的(好了,P3199 就是下一篇文章的素材了)。它的原理也很简单,就是一个点如果被松弛多于 \(1\) 次,就一定存在负环。在一开始,就给当前的点打上 \(\text{vis}\) 标记,最后再取消。我们遍历当前点的邻接点,假设为 \(\text{to}\)。在松弛完毕后立马判断 \(\text{vis}_\text{to}\) 是否为 \(\text{True}\),如果是,则 \(\text{to}\) 之前就被松弛过,现在还能松弛,则一定有负环。否则继续 \(\text{DFS}\),最后返回 \(\text{False}\)。如果递归的结果是 \(\text{True}\),则也有负环,可以写为一个条件判断式,即 \(\text{vis}_\text{to}||\text{spfa(to)}\)。这里有个细节,\(\text{vis}_\text{to}\) 写在前面,因为短路运算符的性质,只要 \(\text{vis}_\text{to}\) 成立,就不会看 \(\text{spfa(to)}\),不再继续 \(\text{DFS}\),节省时间。

\(\text{Code:}\)

bool spfa(int cur) {
    vis[cur] = 1;
    for (auto nxt : nbr[cur]) {
        int to = nxt.to, w = nxt.w;
        if (dis[to] > dis[cur] + w) {
            dis[to] = dis[cur] + w;
            if (vis[to] || spfa(to)) return 1;
        }
    }
    vis[cur] = 0;
    return 0;
}

可以看到,\(\text{DFS}\) 的代码简短的多,但是友情提示一下,这个算法过不了 P3385,会 T 一个点。

BFS-SPFA 对比 DFS-SPFA

如果你去实践了一下,你会发现:诶?DFS 的版本过不了模板,BFS 却能过。DFS 是不是比 BFS 慢?不,听我来分析一下他们的区别。DFS-SPFA 其实比 BFS-SPFA 要快得多。你会发现评测记录中其他测试点基本上都是几毫秒,都没有超过 \(10\text{ms}\)。在一个图中,如果只要判断是否存在负环,DFS-SPFA 是非常快的,这道模板题只是有一个点专门卡 DFS。而如果要在没有负环后输出最短路,则 BFS 会更快。比如下一次要讲的一些习题中,有的就要获得最短路,而有的只要判断负环存在就行了。比如我说的 P3199 最小圈,就只要判断是否存在负环,用 BFS-SPFA 就会 \(\color{#052242}\texttt{TLE}\) 挂 \(50\text{pts}\)。

小结

综上所述,SPFA 判负环的方法就是这些了。请不要忘记关注下一次的文章:负环的习题和应用。

再见!

标签:cur,vis,text,负环,判负,SPFA,dp
From: https://www.cnblogs.com/Weekoder/p/18240226

相关文章

  • 最短路算法之:SPFA 算法
    最短路系列:SPFA算法大家好,我是Weekoder!终于,我们的最短路算法SPFA闪亮登场了!虽然话是这么说,但是我还是建议大家先学习Dijkstra算法,再来学习SPFA。并且我们在这里学习的SPFA算法只能通过P3371,并不能通过标准版的P4779。SPFA的原型——Bellman-Ford在学习SPFA之前,我......
  • 图论-SPFA与差分约束
    闻道有先后,术业有专攻当用来判断负环的时候,SPFA还挺好用的intpre[N];voidprint_path(ints,intt){if(s==t){cout<<s;return;}print_path(s,pre[t]);cout<<""<<t;}inthead[N],cnt;structEdge{intfrom,to,nxt,c;}e[......
  • 套利(spfa判环+STL)
    套利题目描述套利是利用汇率差异实现货币增值。例如,1美元可以兑换0.5英镑、1英镑可以兑换10法郎、1法郎可以兑换0.21美元。接下来,一个聪明的交易商就可以从1美元开始,0.5*10.0*0.21=1.05美元,获得了5%的利润。你的任务是写一个程序,从输入文件读入汇率清单,然后决定套利......
  • [USACO06DEC] Wormholes G(spfa判断环)
    [USACO06DEC]WormholesG题目背景英文题面见此链接题目描述John在他的农场中闲逛时发现了许多虫洞。虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前)。John的每个农场有m......
  • SPFA
    这算是我的第一篇使用LaTeX的文章易写,支持负权,可判负环,可以求最短路,也可以最长路,什么都行。就是容易被卡qwq所以SPFA他死了。是Bellman_Ford算法的队列优化版。使用范围支持负权,可以处理负环,可判负环,可以求最短路,也可以求最长路。平均时间复杂度\(O(m)\),极限时间复杂度为\(......
  • SPFA算法
    单源最短路算法,可以处理负边权,平均时间复杂度\(O(kn)\),最坏时间复杂度\(O(mn)\)问题描述:有一个连通图\(G=(V,E)\),连接节点\(i\)和节点\(j\)的边权写作\(e^i_j\)(\(e^i_j\geq0\)),求从起点(\(s,s\inV\))开始,到其它各个节点(\(d,d\inV-s\))的最短路长度。思路详解:设从起点s到节点d......
  • 最短路算法(Dijkstra + SPFA + Floyd)
    最短路算法(Dijkstra+SPFA+Floyd)Dijkstra算法1.算法基本介绍Dijkstra算法通常是求解单源最短路中最快的算法,但它无法处理存在负权边的情况(原因在正确性证明中)。Dijkstra本质上是一种贪心算法,通过不断调整每个点的“当前距离”最终得到最优结果,其实后面要讲到的几种算法也大......
  • 【学习笔记】spfa
    一、spfa模板:voidspfa(intx){ for(inti=1;i<=n;i++) vis[i]=0,dis[i]=inf; dis[1]=0,f[1]=1; q.push(1); while(!q.empty()) { intt=q.front(); q.pop(); vis[t]=0; for(inti=first[t];i;i=e[i].next) { intto=e[i].to; if(dis[t]+e[i].v<......
  • spfa优化
    \(spfa\)的优化都是基于\(deque\)的,我们通常使用\(LLL\)优化,代码简单,优化效果最好,详情可见参考这里,例题可以参考这里1.\(LLL\)优化(入队优化)LargeLabelLast优化:思路就是将\(dist\)更大的点放入队尾,将\(dist\)更小的点放入队头,优先使用\(dist\)更小的点进行松弛操......
  • 关于spfa
    “正常”求最短路BFS版本voidspfa(){queue<int>q;q.push(0);fl[0]=1;while(q.size()){intx=q.front(); q.pop(); fl[x]=0; for(inti=h[x];i;i=s[i].next){ inty=s[i].to; if(dis[y]<dis[x]+s[i].w){ ......