Tarjan 算法的原理是通过维护每个点的 dfs 序和最小回溯值 low 来判断图的性质。
割点
定义:删去后图的联通分量增加,即图的连通性被破坏的点。
按是否是根节点讨论。
非根节点的:若 \(low_u\ge dfn_u\),则该点为割点。子树内所有点无法回溯到该点以前的点,那么删去该点子树和原图分离,故该点为割点。
根节点:有两个及以上子节点。比较显然。
注意:孤立点不算作割点。
点击查看代码
/*割点*/int iscut[N];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; int son = 0;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){son++;Wtarjan(v, u);low[u] = min(low[u], low[v]);if(low[v] >= dfn[u] && fa) iscut[u] = 1;}else low[u] = min(dfn[v], low[u]);}if(!fa && son >= 2) iscut[u] = 1;}
点双联通分量
定义:删去任意一个点,不改变该分量连通性的极大连通子图。
更易懂的解释为:不含割点的极大图。
性质:
-
两个点双至多存在一个公共点(该点一定是割点);
-
任意一个非割点的点都只存在于一个点双中,割点一定属于两个及以上的点双;
-
对于一个点双(除去孤立边),其内部任意两不同点一定存在两条及以上的点不重复的路径。
那么我们找到割点,也就找到了点双。
实现:实时维护一个栈,若 \(low_v\ge dfn_u\),则将栈内 \(v\) 及以上的点全部弹出作为一个点双,随后将 \(u\) 加入点双。
注意:孤立点算作点双。
点击查看代码
/*点双*/int num, st[N], top;vector<int> ans[N];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; int son = 0; st[++top] = u;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){son++;Wtatjan(v, u);low[u] = min(low[u], low[v]);if(low[v] >= dfn[u]){++num;while(top){ans[num].P_B(st[top--]);if(st[top + 1] == v) break;}ans[num].P_B(u);}}else low[u] = min(low[u], dfn[v]);}if(!fa && !son) ans[++num].P_B(u);}
割边(桥)
定义:删去后图的联通分量增加,即图的连通性被破坏的边。
实现:若一条边为 dfs 树上的边,且 \(low_v\gt dfn_u\),则该边为割边。子树上所有点都无法回溯至该点及以上的点,删去后子树与原图分离,故该边为割边。
注意:链式前向星存图若用 i
与 i^1
来代表一条边的双向,下标需从 2 开始。求割边强制要求判父亲,及不能用 dfs 到该点的反向边更新回溯值。孤立边是割边。
点击查看代码
/*割边*/// 边下标从 2 开始bool isbridge[N << 1];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue; // necessaryif(!dfn[v]){Wtarjan(v, u);low[u] = min(low[u], low[v]);if(low[v] > dfn[u]) isbridge[i] = isbridge[i ^ 1] = 1;}else low[u] = min(low[u], dfn[v]);}}
边双连通分量
定义:删去任意一条边,不改变该分量连通性的极大连通子图。即不存在割边的极大联通子图。
实现:先求割边,将割边删去后,每个连通块为一个边双。
点击查看代码
/*边双*/// 先求割边,将割边删去,剩下的即为边双bool yz[N];vector<int> ans[N];inline void Wtarjan(int u, int now){yz[u] = 1; ans[now].P_B(u);for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(yz[v] || isbridge[i]) continue;Wtarjan(v, now);}}
强连通分量
更常见的形式为缩点。主要存在于有向图。
定义:任意两点相互可达的极大连通子图。
实现:维护一个栈,若一个点遍历完子树后仍有 \(low_u=dfn_u\),则栈内 \(u\) 及以上的点构成一个强联通分量,可缩为一个点。详细原理见此。
点击查看代码
/*强联通分量(缩点)*/int num, bl[N], st[N], top;inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; st[++top] = u;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){Wtarjan(v, u);low[u] = min(low[u], low[v]);}else if(!bl[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){++num;while(st[top] != u) bl[st[top--]] = num;top--, bl[u] = num;}}
完整实现
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
#define P_B(x) push_back(x)namespace Wisadel
{ // Tarjan 全家桶 int hh[N], to[N << 1], ne[N << 1], cnt;int dfn[N], dt, low[N];inline void Wadd(int u, int v){to[++cnt] = v; ne[cnt] = hh[u]; hh[u] = cnt;}/*割点*/int iscut[N];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; int son = 0;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){son++;Wtarjan(v, u);low[u] = min(low[u], low[v]);if(low[v] >= dfn[u] && fa) iscut[u] = 1;}else low[u] = min(dfn[v], low[u]);}if(!fa && son >= 2) iscut[u] = 1;}/*点双*/int num, st[N], top;vector<int> ans[N];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; int son = 0; st[++top] = u;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){son++;Wtatjan(v, u);low[u] = min(low[u], low[v]);if(low[v] >= dfn[u]){++num;while(top){ans[num].P_B(st[top--]);if(st[top + 1] == v) break;}ans[num].P_B(u);}}else low[u] = min(low[u], dfn[v]);}if(!fa && !son) ans[++num].P_B(u);}/*割边*/// 边下标从 2 开始bool isbridge[N << 1];inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue; // necessaryif(!dfn[v]){Wtarjan(v, u);low[u] = min(low[u], low[v]);if(low[v] > dfn[u]) isbridge[i] = isbridge[i ^ 1] = 1;}else low[u] = min(low[u], dfn[v]);}}/*边双*/// 先求割边,将割边删去,剩下的即为边双bool yz[N];vector<int> ans[N];inline void Wtarjan(int u, int now){yz[u] = 1; ans[now].P_B(u);for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(yz[v] || isbridge[i]) continue;Wtarjan(v, now);}}/*强联通分量(缩点)*/int num, bl[N], st[N], top;inline void Wtarjan(int u, int fa){dfn[u] = low[u] = ++dt; st[++top] = u;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;if(!dfn[v]){Wtarjan(v, u);low[u] = min(low[u], low[v]);}else if(!bl[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){++num;while(st[top] != u) bl[st[top--]] = num;top--, bl[u] = num;}}
}
signed main(){}
未经运行检测,如果哪写挂了记得告诉我啊(
祝大家 NOIP rp++!