圖論---tarjan

本篇文章主要是對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

完結撒花

有問題請評論謝謝鴨

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章