Tarjan


Tarjan

1. DFS樹(深度優先搜索樹)

  • 上圖右圖是左圖以1為起點進行DFS時產生的生成樹。

  • 有向圖的 DFS 生成樹主要有 4 種邊(不一定全部出現):

    1. 樹邊(tree edge):綠色邊,每次搜索找到一個還沒有訪問過的結點白點)的時候就形成了一條樹邊。
    2. 返祖邊(back edge):黃色邊,也被叫做回邊,即指向祖先結點(灰點)的邊。
    3. 橫叉邊(cross edge):紅色邊,它主要是在搜索的時候遇到了一個已經訪問過黑點dfn[u]>dfn[v])的結點,但是這個結點 並不是 當前結點的祖先時形成的。
    4. 前向邊(forward edge):藍色邊,它是在搜索的時候遇到子樹中的結點黑點dfn[u]<dfn[v])的時候形成的。
  • 無向圖不存在橫叉邊和前向邊。

2. Tarjan算法求強連通分量

  • 強連通分量(Strongly Connected Components),經常簡寫為:SCC,有向圖中任意兩點間可達,實際上形成一個環。

  • Tarjan基於對圖的深度優先搜索,並對每個節點引入兩個值:

    • dfn[u]:節點u的時間戳,記錄點uDFS過程中第幾個訪問的節點。
    • low[u]:記錄節點uu的子樹不經過搜索樹上的邊(樹邊)能夠到達的時間戳最小的節點。
    • 初始時,dfn[u]==low[u]
  • 對於每一條與u相連的邊<u,v>

    • 若在搜索樹上vu的子節點,即邊<u,v>是樹枝邊,則更新low[u]= min(low[u], low[v])
    • <u,v>不是搜索樹上的邊(反向邊),則更新low[u]= min(low[u], dfn[v])
  • 縮點

    • 在有向圖中,我們經常需要把一個SCC縮成一個點,然后生成一個有向無環圖(DAG),或把一個無向圖縮點后變成一棵樹,然后可以有很多優秀的性質進行解決。
    • 算法實現:
      1. 從圖的某一點u開始,對圖進行DFS(u),點維護dfn[u]值和low[u]值。
      2. DFS時先將u壓入棧中,然后遍歷鄰接邊,鄰接邊定點為v
        • <u,v>為樹邊:DFS(v),回溯時更新:low[u]=min(low[u],low[v])
        • <u,v>為返祖邊:直接更新:low[u]=min(low[u],dfn[v])
      3. 節點u變黑,即其所有子樹訪問結束時,若dfn[u]==low[u]時,此時棧頂節點到節點u,為一個SCC
  • 例題:縮點(洛谷p3387)

    Description
    • 給定一個 n 個點 m 條邊有向圖,每個點有一個權值,求一條路徑,使路徑經過的點權值之和最大。你只需要求出這個權值和。
    • 允許多次經過一條邊或者一個點,但是,重復經過的點,權值只計算一次。
    Input
    • 第一行兩個正整數 n,m
    • 第二行 n個整數,依次代表點權
    • 第三至 m+2 行,每行兩個整數 u,v,表示一條 \(u\rightarrow v\) 的有向邊。
    Output
    • 共一行,最大的點權之和。
    Sample Input
    2 2
    1 1
    1 2
    2 1
    
    Sample Output
    2
    
    Hint
    • 對於 \(100\%\) 的數據,\(1\le n \le 10^4\)\(1\le m \le 10^5\),點權$ \in [0,1000]$。

    分析:

    • 題目說可重復的經過同一個點和邊,但權值只算一次,如果圖是一個強連通圖的話,顯然每個點我們都能走一遍,答案就是所有點的點權和。

    • 我們先對圖進行Tanjan縮點並維護每個點的權值和sum[],縮點后,在原圖的基礎上我們建出新的DAG圖。

    • 在新的DAG圖中,我們可以進行拓撲排序+dp,定義dp[i]表示以節點i為起點的最大點權和,轉移方程:dp[i]=max(dp[j]+sum[i])ji的子節點。

    • Code

      #include <bits/stdc++.h>
      const int maxn=100000+5
      struct edge{
      	int from,to, next;
      }e[maxn << 1],g[maxn << 1];//e原圖,g存儲新圖DAG 
      int lene,leng, heade[maxn],headg[maxn];//圖e和g的邊表 
      int Time, dfn[maxn], low[maxn], vis[maxn],s[maxn],top;//s是模擬棧,要全局 
      int dp[maxn], n, m, sum[maxn], a[maxn], tot, belong[maxn];
      void Inserte(int x, int y){ e[++lene].from=x;e[lene].to =y;e[lene].next=heade[x], heade[x] = lene; }//原圖
      void Insertg(int x, int y){ g[++leng].from=x;g[leng].to =y;g[leng].next=headg[x], headg[x] = leng; }//新圖
      void tarjan(int u){
      	dfn[u] = low[u] = ++Time;//初始化
      	vis[u] = 1;s[++top]=u;//標記並進棧
      	for (int i = heade[u]; i; i = e[i].next){
      		int v = e[i].to;
      		if (!dfn[v]) {//v為白點
      			tarjan(v);
      			low[u] = std::min(low[u], low[v]);//<u,v>為樹枝子節點low值更新父節點low值
      		}//否則有可能是返祖邊
      		else if (vis[v]) low[u] = std::min(low[u], dfn[v]);
      	}
      	if(dfn[u] == low[u]){//縮點
      		++tot;//記錄新圖節點數
      		while(s[top+1]!=u){//從棧頂到u的點縮成一個新點tot
      			int v=s[top];
      			belong[v]=tot;vis[v]=0;sum[tot]+=a[s[top--]];
      		}//belong表示強連通分量編號,vis表示是否在棧中,sum表示強連通分量權值和
      		
      	}
      }
      void dfs(int u){
      	if (dp[u]) return;
      	dp[u] = sum[u];
      	for (int i = headg[u]; i; i = g[i].next){
      		int v = g[i].to;
      		dfs(v);
      		dp[u] = std::max(dp[u], dp[v] + sum[u]);//這里是記憶化
      	}
      }
      int main(){
      	scanf("%d%d",&n,&m);
      	for (int i = 1; i <= n; ++i) scanf("%d",&a[i]);
      	for (int i = 1; i <= m; ++i){
      		int x,y;scanf("%d%d",&x,&y);
      		Inserte(x,y);
      	}
      	for (int i = 1; i <= n; ++i)
      		if (!dfn[i]) tarjan(i);
      	leng = 0;
      	memset(headg, 0, sizeof(headg));
      	for (int i = 1; i <= m; ++i){//遍歷原始邊建新圖
      		int u=e[i].from,v=e[i].to;
      		if (belong[u] != belong[v]) //判斷邊的兩個端點是否在同一個新點中
      			Insertg(belong[u], belong[v]);//不在就建一條邊
      	}
      	int ans=0;		
      	for (int i = 1; i <= tot; ++i)
      		if (!dp[i]){
      			dfs(i);
      			ans = std::max(ans, dp[i]);
      		}
      	printf("%d\n", ans);
      	return 0;
      }
      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM