『笔记』SPFA判断负权环
基本定义
什么是负权环??
负权环指的是图上的某些边首尾相连构成的,边权和相加小于零的一条环。
例如:
下图中 \(1 \to 2\) \(\to 3\) \(\to 1\) 就是一条负权环,权值和为 \(-1\) 。而 \(2 \to 3\) \(\to 4\) \(\to 5\) 则是一条环而不是负权环,其权值和为 \(10\) 。
性质
显而易见,如果某图存在负权环,那么求最短路的时候如果不特殊处理,那么就会导致程序在负权环上一直转圈圈,所谓的最短路权值和就会越转越小,知道程序优美 \(TLE\) 掉。
基本操作
如何判断负权环呢??
死去的SPFA就是一大利器!!
“请求最短路”:关于____,它____。
“本题存在负环”:关于____,它复活了?!
SPFA BFS型
优缺点
-
代码简单,思路简洁,和求最短路的 \(SPFA\) 代码相似度高。
-
比较慢,容易超时。
实现思路
-
对每一个进行最短路求权,对每一条边进行最短路松弛。
-
如果有某一个点入队次数超过节点总数,则说明存在负环。
-
正确性:
对于 \(BFS\) ,一次 \(BFS\) 中,没有负环的情况下一个点最多入队 \(n\) 次(从源点出发连向所有其它点,而所有其他点又指向一个点,每个点正好将这个点更新一次,入队一次)。
如果说还能再更新一次这个点,那一定是这个点出发跑了一个负环再到达自己,\(num\) 在进入是否入队的判断中执行。
代码
bool SPFA_BFS(int st)
{
q.push(st);
vis[st] = true;
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (dis[v] > dis[u] + e[i].dis)
{
dis[v] = dis[u] + e[i].dis; //最短路松弛
num[v] = num[u] + 1; //记录某节点出现次数
if (num[v] >= n) //若大于总节点数,则存在负环
return true;
if (!vis[v])
{
num[v]++;
/*
if(num[v]>=n)
return true;
*/
q.push(v);
vis[v] = true;
}
}
}
}
return false;
}
SPFA DFS型
优缺点
-
效率相对 \(BFS\) 直接起飞。
虽然特殊构造的图还是会爆炸。。。 -
一次质能处理判断一个源点出发是否存在负权环。如果要判断整个图,就需要拓展 \(n\) 个节点。
实现思路
-
在一次搜索中,一旦一个点更新了两次,就说明有负环
-
正确性:
首先搜到一个点两次肯定是有环的,然后第一次更新完了第二次又更新了,除非环的权值是负的,否则不可能更新,于是这样就简单的找到了负环。
代码
bool check(int st)
{
vis[st] = true;
for (int i = head[st]; i; i = e[i].nxt)
{
int v = e[i].to;
if (dis[v] > dis[st] + e[i].dis)
{
dis[v] = e[i].dis + dis[st];
if (!vis[v])
{
if (SPFA_DFS(v)) //前面搜到了负环
return true;
}
else
return true; //一次深搜中搜了一个点两次,则存在负环。
}
}
vis[st] = false;
return false;
}
典型例题
#10085. 「一本通 3.3 练习 2」虫洞 Wormholes
温馨提示:珍爱生命,多组数据记得初始化。