tarjan

ppllxx發表於2024-03-13

縮點

有向圖縮點(把一個強連通分量看成一個點),用於最佳化。

  • 樹枝邊:DFS 時經過的邊,即 DFS 搜尋樹上的邊
  • 反祖邊:也叫回邊或後向邊,與 DFS 方向相反,從某個結點指
    向其某個祖先的邊
  • 橫叉邊:從某個結點指向搜尋樹中另一子樹中的某結點的邊,它
    主要是在搜尋的時候遇到了一個已經訪問過的結點,但是這個結
    點並不是當前結點的祖先時形成的
  • 前向邊:與 DFS 方向一致,從某個結點指向其某個子孫的邊,
    它是在搜尋的時候遇到子樹中的結點的時候形成的

對於每個點維護兩個值 \((dfn,low)\)\(dfn\)\(dfs\) 序,\(low\) 表示這條路走到頭能回到祖宗的最小值(或葉子),對於一個點 \(u\) ,如果他的 \(low[u]\) 等於 \(dfn[u]\) ,說明 向下 \(dfs\) 時還能回到 \(u\) ,則中間這部分構成一個強連通分量。
如圖,先 \(dfs\)\(e\)\(dfn[e]==low[e]\),發現一個強連通分量,\(e\) 出棧,回溯到 \(b\) ,第二個強連通分量,將 \(b,c,d\) 出棧。

code
void tj(int s)
{
	dfn[s]=low[s]=++num;
	v[s]=1; st.push(s);
	for(int i=head[s];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!dfn[to])
		{
			tj(to);
			low[s]=min(low[s],low[to]);
		}
		else if(v[to]) low[s]=min(low[s],dfn[to]);
	}
	if(dfn[s]==low[s])
	{
		int now;
		t++;
		do
		{
			now=st.top(); st.pop();
			v[now]=0; bl[now]=t;
			sz[t]++;
		} while(s!=now);
	}
}

割點

無向圖求割點,就是刪掉後能把圖隔成幾個部分的點,思路和縮點類似,如果 \(dfn[u]<=low[v]\) ,說明 \(u\) 以下有一個連通塊,且 \(u\) 下面的連通塊和 \(u\) 上面的沒有聯絡,所以此時 \(u\) 為割點。
如圖 \(B,E,K\) 為割點。
(注意,根節點如果只有一個兒子,他不是割點。)

code
void tj(int s)
{
	dfn[s]=low[s]=++num;
	int son=0;
	for(int i=head[s];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!dfn[to])
		{
			tj(to);
			low[s]=min(low[s],low[to]);
			if(dfn[s]<=low[to])
			{
				son++;
				if(s!=root || son>1)
				{
					if(!ans[s]) cnt++;
					ans[s]=1;
				}
			}
		}
		else	
			low[s]=min(low[s],dfn[to]);
	}
}

相關文章