圖聯通性問題(Tarjan)整理
阿新 • • 發佈:2021-11-20
笑死,根本學不會
笑死,根本學不會。(爛尾力
Tarjan演算法是用於處理圖連通性相關的一類演算法。
1、強連通分量、雙連通分量、割點與橋的定義
見 OI wiki
2、Tarjan演算法的基本思想與框架
本質是通過構建 dfs 生成樹,然後處理非樹邊來求出連通性相關的這些量。
dfs 生成樹的標記通過dfn序(dfs到某個點的順序構建),處理非樹邊需要一個low值(通過望子樹走的樹邊或者子樹上的至多一條非樹邊能到達的dfn值最小的點)。
void tarjan(int x) { low[x]=dfn[x]=++num; for(int i=H[x];i;i=K[i]) { int y=V[i]; if(!dfn[y]) tarjan(y,i),low[x]=min(low[x],low[y]); else low[x]=min(low[x],dfn[y]); } }
3、強連通分量
非樹邊有三種:回邊,橫叉邊,前向邊。
-
前向邊對答案無貢獻,不作討論。
-
回邊一定有貢獻,且滿足low值的條件,更新為low。
-
橫叉邊只有指向能回到dfs樹上祖先的點才有貢獻。
綜上,我們可以用一個棧記錄一些節點,這些節點要麼是當前點的祖先節點,要麼能夠到達祖先節點。
每dfs到時節點入棧。
不在棧中的橫叉邊指向節點不能更新low值,因為這些點回不到祖先,不能產生貢獻。
當存在點 \(dfn(x)=low(x)\) 時說明以x為根的子樹構成了一個強連通分量,無法從此到達祖先節點,所以x及其上方的節點退棧,並計為一個強連通分量。
void tarjan(int x) { low[x]=dfn[x]=++num; st[++top]=x,ins[x]=1; for(int i=H[x];i;i=K[i]) { int y=V[i]; if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]); else if(ins[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]) { ++cnt; for(;st[top]!=x;) { bl[st[top]]=cnt,a2[cnt]+=a[st[top]]; ins[st[top]]=0,--top; } bl[x]=cnt,a2[cnt]+=a[x]; ins[x]=0,--top; } } //in main for(int x=1;x<=n;x++) if(!dfn[x]) tarjan(x);
4、橋與邊雙連通分量
\(dfn(x)<low(y)\)
void tarjan(int x,int in_e) { low[x]=dfn[x]=++num; for(int i=H[x];i;i=K[i]) { int y=V[i]; if(!dfn[y]) { tarjan(y,i),low[x]=min(low[x],low[y]); if(low[y]>dfn[x]) { int u=V[i],v=V[i^1]; if(u>v) swap(u,v); bdg.push_back(mkp(u,v)); } } else if(i!=(in_e^1)) low[x]=min(low[x],dfn[y]); } } //in main for(int x=1;x<=n;x++) if(!dfn[x]) tarjan(x,-1);
5、割點與點雙連通分量
\(dfn(x) \leq low(y)\)
注意根節點需要兩個滿足條件的 \(y\)。
void tarjan(int x,int rt)
{
low[x]=dfn[x]=++num;
int flag=0;
for(int i=H[x];i;i=K[i])
{
int y=V[i];
if(!dfn[y])
{
tarjan(y,rt),low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
++flag;
if(x!=rt || flag>1) cut[x]=1;
}
}
else
low[x]=min(low[x],dfn[y]);
}
}