连通性相关
強(qiáng)聯(lián)通分量
強(qiáng)連通:有向圖 \(G\) 強(qiáng)連通表示,\(G\) 中任意兩個(gè)結(jié)點(diǎn)連通。
強(qiáng)連通分量( Strongly Connected Components ,簡(jiǎn)稱 \(\operatorname{SCC}\) ):極大的 強(qiáng)連通子圖。
Tarjan
維護(hù)了以下兩個(gè)變量:
-
\(dfn\) :深度優(yōu)先搜索遍歷時(shí)結(jié)點(diǎn) \(u\) 被搜索的次序 。
-
\(low\) :設(shè)以 \(u\) 為根的子樹(shù)為 \(subtree(u)\) 。 \(low\) 定義為以下結(jié)點(diǎn)的 \(dfn\) 的最小值: \(subtree(u)\) 中的結(jié)點(diǎn);從 \(subtree(u)\) 通過(guò) 一條 不在搜索樹(shù)上的邊能到達(dá)的結(jié)點(diǎn) 。
從根開(kāi)始的一條路徑上的 \(dfn\) 嚴(yán)格遞增,\(low\) 嚴(yán)格非降。
對(duì)于一個(gè)連通分量圖,我們很容易想到,在該連通圖中有且僅有一個(gè) \(dfn[u]=low[u]\) 。該結(jié)點(diǎn)一定是在深度遍歷的過(guò)程中,該連通分量中第一個(gè)被訪問(wèn)過(guò)的結(jié)點(diǎn),因?yàn)樗?\(dfn\) 值和 \(low\) 值最小,不會(huì)被該連通分量中的其他結(jié)點(diǎn)所影響。
因此,在回溯的過(guò)程中,判定 \(dfn[u]=low[u]\) 的條件是否成立,如果成立,則棧中從 后面的結(jié)點(diǎn)構(gòu)成一個(gè) \(\operatorname{SCC}\) 。
P2341 [HAOI2006]受歡迎的牛 G \(-\) 模板
$\texttt{code}$ #define Maxn 10005 #define Maxm 50005 void tarjan(int u) {dfn[u]=low[u]=++Time; s.push(u),ins[u]=true;for(int i=hea[u];i;i=nex[i]){if(!dfn[ver[i]]) tarjan(ver[i]),low[u]=min(low[ver[i]],low[u]);else if(ins[ver[i]]) low[u]=min(dfn[ver[i]],low[u]);}if(dfn[u]==low[u]){sum+=1;do{belong[u]=sum;u=s.top(); s.pop(); ins[u]=false;cnt[sum]+=1;} while(dfn[u]!=low[u]);} }for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);時(shí)間復(fù)雜度 \(O(n+m)\) 。
Kosaraju
復(fù)雜度 \(O(n+m)\) 。
Garbow
復(fù)雜度 \(O(n+m)\) 。
我們可以利用強(qiáng)聯(lián)通分量將一張圖的每個(gè)強(qiáng)連通分量都縮成一個(gè)點(diǎn)。
然后這張圖會(huì)變成一個(gè) \(\operatorname{DAG}\),可以進(jìn)行拓?fù)渑判蛞约案嗥渌僮?。
應(yīng)用 \(-\) 縮點(diǎn)
P3387 【模板】縮點(diǎn)
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=tot[0];i++)if(belong[fro[0][i]]!=belong[ver[0][i]])add(1,belong[fro[0][i]],belong[ver[0][i]]),ind[belong[ver[0][i]]]++; topo();割點(diǎn)與橋
在無(wú)向圖中刪去這個(gè)點(diǎn) \(/\) 邊會(huì)使極大強(qiáng)聯(lián)通增大,那么這個(gè)點(diǎn) \(/\) 邊為割點(diǎn) \(/\) 橋 。
注意這里的 \(dfn\) 表示不經(jīng)過(guò)父親,能到達(dá)的最小的 \(dfn\) 。
割點(diǎn)
P3388 【模板】割點(diǎn)(割頂)
關(guān)鍵條件:
-
若 \(u\) 是根節(jié)點(diǎn),當(dāng)至少存在 \(2\) 條邊滿足 \(low[v] >= dfn[u]\) 則 \(u\) 是割點(diǎn) 。
-
若 \(u\) 不是根節(jié)點(diǎn),當(dāng)至少存在 \(1\) 條邊滿足 \(low[v] >= dfn[u]\) 則 \(u\) 是割點(diǎn) 。
割邊(橋)
關(guān)鍵條件:
- 當(dāng)存在一條邊條邊滿足 \(low[v] > dfn[u]\) 則邊 \(i\) 是割邊
關(guān)鍵部分的代碼:
注意:記錄上一個(gè)訪問(wèn)的邊時(shí)要記錄邊的編號(hào),不能記錄上一個(gè)過(guò)來(lái)的節(jié)點(diǎn)(因?yàn)闀?huì)有重邊)!!!
$\texttt{code}$ void tarjan(int x,int Last_edg) {dfn[x]=low[x]=++Time;for(int i=hea[x];i;i=nex[i]){if(!dfn[ver[i]]){tarjan(ver[i],i);low[x]=min(low[x],low[ver[i]]);if(low[ver[i]]>dfn[x]) edg[i]=edg[i^1]=1;}else if(i!=(Last_edg^1)) low[x]=min(low[x],dfn[ver[i]]);} }for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0); for(int j=2;j<=tot;j+=2) ans+=tag[j];雙聯(lián)通分量
邊雙聯(lián)通分量
顯然,找出每一個(gè)橋,去掉這些橋之后的每一個(gè)聯(lián)通塊都是一個(gè)邊雙聯(lián)通字圖。
注意:用邊雙縮點(diǎn)的時(shí)候先處理出割邊,之后用 \(\text{dfs}\) 求出每一個(gè)雙聯(lián)通分量,不用棧!!
例題:P2860 [USACO06JAN]Redundant Paths G
$\texttt{solution}$一句話題意:要將原圖轉(zhuǎn)化為邊雙聯(lián)通圖需要添加的最少邊數(shù)
我們可以先將所有的橋找出來(lái),并同時(shí)對(duì)所有邊雙縮點(diǎn),會(huì)得到一顆縮完點(diǎn)的、由橋構(gòu)成的“樹(shù)”。
我們發(fā)現(xiàn)這棵“樹(shù)”上“葉子結(jié)點(diǎn)”的個(gè)數(shù)除二向上取整就是需要添加的邊的條數(shù)。
點(diǎn)雙連通分量
小粉兔的圓方樹(shù)——點(diǎn)雙詳解
【模板】點(diǎn)雙連通分量
回憶 \(low\) 的定義,就是 \(x\) 的子樹(shù)內(nèi)最多經(jīng)過(guò)一條反祖邊或一條向父親的邊能夠到達(dá)的最小的 \(dfn\) 值。
當(dāng) \(x\) 不是這個(gè)連通塊的根時(shí),如果存在一條邊滿足 \(low(ver)\ge dfn(x)\),那么 \(x\) 就是一個(gè)個(gè)點(diǎn)。
而 \(x\) 是根時(shí),\(x\) 需要存在至少兩條邊滿足以上條件。
這是因?yàn)楫?dāng) \(x\) 為根時(shí),沒(méi)有父親與之相連。
那么當(dāng)我們?cè)谇簏c(diǎn)雙時(shí),只需要判斷子樹(shù)內(nèi)的 \(low\) 是否會(huì) \(<dfn(x)\),若會(huì),則說(shuō)明子樹(shù)中的點(diǎn)能夠到達(dá) \(x\) 的祖先,\(x\) 必然不是個(gè)點(diǎn)。而若不會(huì),即 \(low(ver)\ge dfn(x)\),則說(shuō)明 \(x\) 是這個(gè)點(diǎn)雙的頂端個(gè)點(diǎn),這時(shí)可以將遞歸進(jìn)入的點(diǎn)都彈出,直至棧頂元素變?yōu)?\(ver\),再將 \(ver\) 從棧中彈出,加上 \(x\) 就是這個(gè)點(diǎn)雙聯(lián)通分量了。
還要注意孤立點(diǎn)的情況。
如果需要處理點(diǎn)雙中點(diǎn)的個(gè)數(shù),那么可以在棧中存放點(diǎn),例如一下代碼:
void tarjan(int x) {dfn[x]=low[x]=++Time,sta[++tp]=x;if(tp==1 && !hea[x]) { SCC[++sum].pb(x); return; }for(int i=hea[x];i;i=nex[i]){if(!dfn[ver[i]]){tarjan(ver[i]);low[x]=min(low[x],low[ver[i]]);if(low[ver[i]]>=dfn[x]){sum++;do { SCC[sum].pb(sta[tp--]); }while(sta[tp+1]!=ver[i]);SCC[sum].pb(x);}}else low[x]=min(low[x],dfn[ver[i]]);} }而如果需要處理點(diǎn)雙中的邊,那么就需要在棧中存邊,如以下代碼:
void tarjan(int x,int fa) {dfn[x]=low[x]=++Time;int cntson=0;for(int i=hea[x];i;i=nex[i]){if(!dfn[ver[i]]){sta[++tp]=i,tarjan(ver[i],i),cntson++;low[x]=min(low[x],low[ver[i]]);if(low[ver[i]]>=dfn[x]){isdian[x]=true,sum++;int tmp=0;do{tmp=sta[tp--];scc[sum].pb(fro[tmp]);scc[sum].pb(ver[tmp]);}while(tmp!=i);}}else if(i!=(fa^1)) low[x]=min(low[x],dfn[ver[i]]); }if(fa==0 && cntson==1) isdian[x]=false; }總結(jié)
- 上一篇: OPPO A2 手机官宣 11 月 11
- 下一篇: 期望 概率DP