一種科學的判斷負環的方法
判斷負環的方法
------------ 這裏有個叫分界線的家夥突然想說,本章主體思路都是在 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;
}
一種科學的判斷負環的方法