1. 程式人生 > 其它 >圖聯通性問題(Tarjan)整理

圖聯通性問題(Tarjan)整理

笑死,根本學不會

笑死,根本學不會。(爛尾力

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]);
	}
}