Tarjan 全家桶
Tarjan 算法的原理是通过维护每个点的 dfs 序和最小回溯值 low 来判断图的性质。
割点
定义:删去后图的联通分量增加,即图的连通性被破坏的点。
按是否是根节点讨论。
非根节点的:若
根节点:有两个及以上子节点。比较显然。
注意:孤立点不算作割点。
点击查看代码
/*割点*/
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);
}
割边(桥)
定义:删去后图的联通分量增加,即图的连通性被破坏的边。
实现:若一条边为 dfs 树上的边,且
注意:链式前向星存图若用 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; // necessary
if(!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;
}
}
完整实现
点击查看代码
#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; // necessary
if(!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++!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】