tarjan縮點(洛谷P387)

  • 2019 年 10 月 3 日
  • 筆記

此題解部分借鑒於九野的博客

題目分析

  • 給定一個 (n) 個點 (m) 條邊有向圖,每個點有一個權值,求一條路徑,使路徑經過的點權值之和最大。你只需要求出這個權值和。
  • 允許多次經過一條邊或者一個點,但是,重複經過的點,權值只計算一次。

  • 假如沒有後面這條限制的話,那圖一定是一個無環圖。因為有環的話我可以一直在環上跑,所以答案就沒有一個上界

  • 沒有環的話我萌可以很自然地想到一個 (O(n)) 的 拓撲(dp) 做法,先做入度為 (0) 的點,更新入度不為 (0) 的點,把更新後入度為 (0) 的點加入隊列里,繼續做之前的事情

  • 現在考慮有環怎麼做

  • 有一個貪心的思路是,到環上就先把環上的點都走完,再從環上任意一點出發

  • 其實這個環可以看做一個大點,也就是我們今天要介紹的主角 (to) 縮點


tarjan縮點

下面這張圖是從 九野的博客copy 過來的

  • 把可以互相抵達的點集叫做一個連通分量
  • 最大的那個可以互相抵達的點點集即為強連通分量
  • 特別的,單個的點也可以是強連通分量

比如說 :({ 4, 5 }) 是一個聯通分量,而 ${4,5,6 } $ 則是一個強連通分量(一個大點)

tarjan的過程就是通過 dfs 找強連通分量(大點)的過程

對圖dfs一下,遍歷所有未遍歷過的點 ,會得到一個有向樹,顯然有向樹是沒有環的。(注意搜過的點不會再搜

則能產生環的 只有(指向已經遍歷過的點)的邊

我們發現 (7 to 3)紅邊 / 橫叉邊)這種邊一定不會產生連通分量

(6 to 4)綠邊 / 返祖邊)這種邊一定會產生聯通分量

具體來說:具有父子關係的邊一定會產生聯通分量

我們在dfs的時候需要用一個來保存當前所在路徑上的點(棧中所有點一定是有父子關係的)

我們用一些數組來表示dfs的過程

int tim, dfn[MAX], low[MAX]

(dfn[i]) 表示遍歷到節點 (i) 的時間戳(第幾次遍歷)

(low[i]) 表示往上可以到達最早的點

初始化 (dfn[i] = low[i] = ++tim)

我們可以根據上面過程的步驟寫出以下代碼

    for(int i = head[u]; i; i = nex[i]) {          if(dfn[to[i]]) {              if(instack[to[i]]) {                  if(low[to[i]] < low[u]) {                      low[u] = low[to[i]];                  }              }          } else {              dfs(to[i]);              if(low[to[i]] < low[u]) {}                  low[u] = low[to[i]];              }          }      }

假如當前節點 (u)(dfn[u] == low[u]) 那就說明棧頂元素一直到節點 (u) 都屬於一個強聯通分量感性理解

把棧中元素彈出並且把他們都給標記為同一種顏色(同一個強連通分量)

    if(dfn[u] == low[u]) {          ++totcol;          do {              int v = stk[top];              col[v] = totcol;              instack[v] = false;          } while(stk[top--] != u);      }

具體實現看代碼

(color {Deepskyblue} {Code})

這裡還有一道 tarjan練手好題