本篇文章主要是對tarjan不同用法的講解 , 其主要目的是複習,當然初學也可以看鴨極有可能看不懂
目錄 :1.有向圖縮點
2.無向圖割點
3.點雙連通分量
1.有向圖縮點
首先來一道經典題目
給定一個 n個點 m 條邊有向圖,每個點有一個權值,求一條路徑,使路徑經過的點權值之和最大。你只需要求出這個權值和。
允許多次經過一條邊或者一個點,但是,重複經過的點,權值只計算一次。
洛谷3387 縮點
這個題的話很明顯我們要用tarjan進行縮點,在圖中尋找強連通分量對吧,因爲這個點如果我們可以走過去的話,那麼他所在的連通分量我們也可以都走一遍在回到這裏。
當我們縮晚點之後,剩下的就一定是一個有向無環圖(DAG)
證明的話可以用反證法 , 如果還有環的話就肯定還有連通 ,但這顯然不可能
然後我們在這個圖上進行一個dp + 拓撲就可以了(這兩個不重要)
所以現在我們就來縮點!!! 先上代碼!
#include<bits/stdc++.h>
using namespace std ;
const int M = 100000 + 5 ;
vector<int> g[M] ;
int bccnum[M] ;
int dfn[M] , low[M] ;
int zhi[M] , z[M] , vis[M] , sum[M] , ru[M];
int ans ;
int gx[6666][6666] , dis[6666];
stack<int> s ;
int cloc = 0 ;
int bccnt = 0 ;
inline void tarjan( int u , int fa)
{
dfn[u] = low[u] = ++cloc ;
s.push(u) ;
for( int i = 0; i < g[u].size() ; i++)
{
register int nxx = g[u][i] ;
if( !dfn[nxx])
{
tarjan(nxx , fa) ;
low[u] = min( low[u] , low[nxx] ) ;
}
else if(!bccnum[nxx])low[u] = min( low[u] , dfn[nxx]) ;
}
if( low[u] == dfn[u])
{
++bccnt;
while(1){
int e = s.top() ;
s.pop() ;
bccnum[e] = bccnt;
sum[bccnt] += z[e] ;
if(e==u)
break ;
}
}
}
int main(){
int n , m ;
cin >> n >> m ;
for( int i = 1 ; i <= n ; i++)
{
cin >> z[i] ;
}
for( int i = 0 ; i < m ; i++)
{
register int a , b ;
scanf("%d%d" , &a , &b) ;
g[a].push_back(b) ;
}
for( int i = 1 ; i <= n ; i++)
{
if( !dfn[i] )
tarjan(i , i);
}
topu() ;
cout << ans ;
return 0 ;
}
我只留下了與tarjan相關的部分
考慮一個強連通分量C , 假設第一個被發現的點是x , 則其他點都是他的後代 , 這樣我們可以在一個DFS中把他們找出來。
現在我們先把這x找出來。對於x的子孫們來說 , 他們所能到達的祖先點最多就是x,並且不是自己,所以在這個強連通分量(SCC)中只有x自己是low【x】 = dfn【x】的;
所以說當我們找到這個x的時候 , 我們可以一起把他的子孫們也放到這個SCC
裏面 ,這也是我們需要棧的原因;
好了 , 說這麼多隻要記住 :
在有向圖中求SCC的時候是low == dfn , 然後出棧的時候要把這個點也放到這SCC中即可
還有一點 :else if(!bccnum[nx])low[u] = min( low[u] , dfn[nx]) ;
這句話別忘了前面的條件(可能是我太弱了經常忘qwq)就是說可能你指向的這個點是在你之前被訪問過了, 但僅僅只是你單方面想過去人家並不喜歡你
所以是不可以更新的!!!
2.無向圖縮點
這個比較簡單,上代碼, 裏面有註釋
inline void tarjan( int u , int fa)
{
dfn[u] = low[u] = ++cloc ;
int child = 0 ;
for( int i = head[u] ; i!=0 ; i = pre[i].mark)
{
register int nxx = pre[i].xnt ;
if( !dfn[nxx])
{
tarjan(nxx , fa) ;
low[u] = min( low[u] , low[nxx] ) ;
if( low[nxx] >= dfn[u] && u != fa)//如果說他的孩子們所能到達他的上面,他就不是割點, 不然就是!
cut[u] = 1 ;
if( u == fa)
child++ ;
}
low[u] = min( low[u] , dfn[nxx]) ;
}
if( u == fa && child >= 2)//如果說是根節點,只要有兩個以上的孩子就可以割了
cut[u] = 1 ;
}
3.點雙連通分量
首先明確點雙連通的定義: 任意兩點存在點不重複的路徑;
所以點雙連通分量的極大子圖爲點雙連通分量(BCC)
然後這個是在無向圖中廢話,但是我們是把邊推進棧中 ,因爲不同BCC可能有重複的點 !!!
然後這個看看代碼應該可以懂
提供樣例:1-2 2-3 1-3 3-4 4-5 5-6 這個圖中兩個BCC爲{1 , 2 ,3 } 和{3 , 4, 5} ;
vector<int> bcc[maxn] // 這裏用vector存的是一個BCC中有哪些點
//bccnum存的是這個點位於哪一個BCC
//但是不是所有點都可以 , 比如樣例中的3 因爲它同時存在於兩個BCC中
if(low[v] >= dfn[u]){
bcccnt++ ;
for( ; ; ){
edge x = s.top() ; s.pop() ;
if(bccnum[x.u] != bcccnt){
bcc[bcccnt].push_back(x.u)
bccnum[x.u] = bcccnt ;
}
if(bccnum[x.v] != bcccnt) {
bcc[bcccnt].push_back(x.v)
bccnum[x.v] = bcccnt ;
}
if(x.u == u && x.v == v) break ;
}
}
這個點雙連通代碼來自lrj的藍書 請讀者自行思考
主要是因爲感覺不常用到, 但還是要知道鴨!!!
總結
有向圖縮點 :low[u]==dfn[u]
無向圖割點:dfn[u]<=low[v]
點雙連通分量:同上 , 但是入棧的是邊
感謝觀看,碼字不易 ,點個贊吧QWQ
完結撒花
有問題請評論謝謝鴨