縮點
有向圖縮點(把一個強連通分量看成一個點),用於最佳化。
- 樹枝邊: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]);
}
}