or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
SCC
Strongly Connected Components,中文即"強連通分量"


意旨為在一個強連通分量中,任意兩點均有大於一種路徑可到達,也就是在一個SCC中不管從哪裡開始,都可以走完全部的點。
或是
像這樣內部可互相抵達的圈圈我們可以把它縮成一個點,意即縮點。
這方法會在某些題目上用到,像是這題。題目要求一個國王的統治區域內要任兩點均可抵達,並且編號其王國,這在作法上就可以用SCC來解決:把任兩點可以抵達的區域縮點,接著把所有縮點編號,就大功告成了。
有兩種演算法可以做到分別為:
Tarjan's SCC Algorithm
此演算法重點在維護三個變數,分別為dfn、up、stk
接著,在遞迴子樹的時候 :
最後遞迴完所有子樹節點時,判斷該點的dfn有沒有等於UP,如果dfn==up代表該點不能再繞上去了,表示找到一個SCC,並且該點為Root,開始pop出stack裡的節點作為該SCC的元素直到當前節點,因為子樹有可能有其他的SCC,所以stack的用意就是維護在遞迴時按照順序的節點,在pop出節點時也才會是照遞迴順序返pop出(子樹的SCC節點已被pop出)。
至於為什麼不能在繞上去就是一個SCC的Root呢,假設一個節點可以繞上去,就代表走完這條路可以回到該點走別條路,符合SCC的性質,如果不能再繞上去說明該點的父節點只能往下,不符合SCC性質,不能合併到下面的SCC,所以在交界處的節點就會是dfn==up。
Kosaraju's Algorithm
此演算法會用到兩個dfs,分別是順向和反向
原理 :
因為一個SCC不管是正向圖或是反向圖都不會影響其性質(兩點間皆有至少一路徑可達),可互相走到的點還是可以走到(想成倒退走),所以正反dfs都可達到的點群即為一個SCC。
在第一個dfs時,要找到最上游(圖左邊)的節點編號,依序往下游(圖右邊)排列,Stack的Top也就是Root,這樣才能在第二個dfs時,從下游(正向圖上游)開始建立SCC,讓在之後建立SCC時能把更下游的節點(已被建立為SCC的節點)篩掉,此動作跟Tarjan把子樹先pop出stack有異曲同工之妙。
Original graph




After reversing the original graph:
靠近Root的點(先走到的點)在正向圖中為上游,在反向圖中就會變成下游(原本是我走向別人變成別人走向我),本來可以從上游SCC走到下游SCC,但在反向圖中,唯一一條能走向別的SCC的路被堵住了(變成反向了),所以從Stack[Top]開始走第二次dfs時只要能走到的點就是同一個SCC。
Cut Point
如果一個點不是割點,其所有子樹都可以繞到此點的祖先,換句話說,只要有一個子樹不能繞到此點的祖先,那該點就是割點
在dfs時,記錄一個時間戳記(dfn),用來表示此節點的祖先編號,因為祖先一定會先遍歷到,dfn較小,可用來判斷能否繞上去
如果要走的點的dfn已經被賦予值,代表該點為祖先,直接更新low[now],取min:能繞上去的最小祖先,如果沒有被遍歷過,代表該點為子樹,就往下dfs,對於每個未走過子樹,dfs完後檢查該子樹能否走到此點的祖先(if low[i]<=dfn[now]代表該點能走到的最小祖先有比此點還上面),如果不能,就代表此點為割點,因為第一段的結論:"只要有一個子樹不能繞到此點的祖先,那該點就是割點"
唯一要注意的是根結點,因為其他節點都有father,唯獨根結點沒有,其他節點當割點時可以切割father以上的節點和割點以下的節點,但因為根結點沒有father,所以必須要兩個以上的子樹才能成為割點
學習紀錄
Q : 為什麼在Tarjan演算法中如遇到該點已走過時只需用dfn[i]更新就好了,要取min的話,用low[i]來更新不是更好嗎?
A : 因為在Tarjan的重點在於「能不能繞上去」,而不是「能繞上去最高到哪裡」,因此用low[i]或dfn[i]來更新都是可以的。另外一個觀察是dfn[i]一定會比當前的dfn[now]小,因為dfn是按走過的點嚴格遞增的,所以只要走到走過的點就都會是能繞上去的祖先,還有一個前提是圖要是有向圖。
這題最初的想法 : 先把所有入度為0的點都dfs一次,接著再看剩下沒被dfs到的就代表是環

但此做法並非最佳解,可能會有以下狀況
有可能會先搜尋到下面三個點,就變成要跑兩次。