一种科学的判断负环的方法
判断负环的方法
------------ 这里有个叫分界线的家伙突然想说,本章主体思路都是在 SPFA 上的o ------------
感觉分为两种大方向,\(BFS\) 和 \(DFS\)
快速写一下 \(BFS\) 的思路
由 \(SPFA\) 的算法可以发现,如果要更新一个点的 \(dis\) ,那么一定有一个点先被更新了以后,然后通过这个新更新的点来更新这个点,那么在没有负环的情况下,一个点能被更新的最多次数为 \(n\) 次,也就是说,如果一个点进队的次数大于 \(n\) 了,嘿嘿嘿~
但是,既然 \(SPFA\) 都能被卡,卡这个感觉很容易的说。。。。(毕竟期望复杂度。。。期望。。。)
--------- 这里又有个叫分界线的家伙说,你可以假装会了 \(BFS\) 的方法了 ---------
好了,接下来是我主要想说的 \(DFS\) 的方法
我们先脑补一下如果有负环的话按照 \(DFS\) 的思路会怎么样呢?
顺着这条路一直走,走着走着,诶!我怎么又走回到自己身上来了呢?
所以自然而然的,我们有了一个新的思路,如果说一条路径上这个点出现了两次,那么,嘿嘿嘿~ x2
然而当你仔细思考以后,你可能有一个逐渐会有一个妙妙的感觉,等等,这条路径上面感觉有故事啊~
故事是这样讲的:我一定有一种走法可以保证我在走这条路的时候我的路径长度一直为负!(不信你先手玩两组样例试一试?)
故事讲完以后,像我这样的蒟蒻一定会想,为啥啊?然后,脑回路清奇的我抱着这个结论显然正确的信仰,花式构思了一种奇奇怪怪的方法来尝试证明。
证明如下:
我们假设这条路径为 \(a_1\ \rightarrow \ a_2\ \rightarrow \ a_3\ \rightarrow \ a_4\ \rightarrow \ ...\ \rightarrow\ a_n\) (显然因为是环,\(n\) 又连到了 \(1\) )
当 \(n = 2\) 的时候,显然 \(a_1 + a _ 2 < 0\) ,一定有一条边是负的,一条边是正的,并且负边的长度的绝对值大于正边。结论显然成立。
好,我们假设 \(n\) 的时候结论成立,来看一看 \(n +1\) 的时候,这个路径会是什么样。
$a_1\ \rightarrow \ a_2\ \rightarrow \ a_3\ \rightarrow \ a_4\ \rightarrow \ ...\ \rightarrow\ a_n \rightarrow \ a_{n+1} $
假设我们从 \(1\) 号点开始走,走到 \(s\) 号点的时候发现,诶,我怎么变正了?
好,那么我们假设出去这条 \(a_s\) 边的总长度为 \(L\) ,显然 \(L\) 为负,并且 \(L\) 的绝对值一定大于 \(a_s\)
那么我就从 \(s+1\) 这个点开始走,按照环的顺序一直走到 \(s-1\) 这个点,这是一条由n个点组成的负环的路径,由于我们已经假设了 \(n\) 个点组成的路径一定有一种合法的路径,那么我按这个方法走完以后,最后走 \(a_s\) 这条边,好了,我们就成功找到了一种 \(n+1\) 个点的合法方案。
数学归纳法大法好!
接下来,再贴一张我刚刚写出的代码~
题目描述
暴力枚举/SPFA/Bellman-ford/奇怪的贪心/超神搜索
输入输出格式
输入格式:
第一行一个正整数T表示数据组数,对于每组数据:
第一行两个正整数N M,表示图有N个顶点,M条边
接下来M行,每行三个整数a b w,表示a->b有一条权值为w的边(若w<0则为单向,否则双向)
输出格式:
共T行。对于每组数据,存在负环则输出一行"YE5"(不含引号),否则输出一行"N0"(不含引号)。
输入输出样例
输入样例:
2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8
输出样例:
N0
YE5
说明
N,M,|w|≤200 000;1≤a,b≤N;T≤10 建议复制输出格式中的字符串。
此题普通Bellman-Ford或BFS-SPFA会TLE
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+ 5;
struct lpl{ int to, dis; }lin;
vector<lpl> point[maxn];
int n, m, dis[maxn];
bool Flag, vis[maxn];
inline void connect(int a, int b, int w){ lin.to = b; lin.dis = w; point[a].push_back(lin); }
inline void putit()
{
int a, b, w;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) point[i].clear();
for(int i = 1; i <= m; ++i)
{
scanf("%d%d%d", &a, &b, &w);
connect(a, b, w);
if(w > 0) connect(b, a, w);
}
}
void SPFA(int t)
{
int now; vis[t] = true;
for(int i = point[t].size() - 1; i >= 0; --i)
{
now = point[t][i].to;
if(dis[now] > dis[t] + point[t][i].dis)
{
dis[now] = dis[t] + point[t][i].dis;
if(vis[now] == true || Flag == true) { Flag = true; break; }
SPFA(now);
}
}
vis[t] = false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
putit();
memset(dis, 0, sizeof(dis)); memset(vis, false, sizeof(vis)); Flag = false;
for(int i = 1; i <= n; ++i) { SPFA(i); if(Flag == true) break; }
if(Flag == true) { printf("YE5\n"); continue;}
printf("N0\n");
}
return 0;
}