Tarjan算法的应用
本文不介绍算法实现(毕竟其他算法实现太多讲的了),只提供自己整理的模板(我认为我这版代码看应该最舒服了)
- 一些概念:
- 割点
- 割边
- SCC
- DCC
- Tarjan算法:
- 时间戳 d f n [ i ] dfn[i] dfn[i]
- 搜索树
- 回溯值 l o w [ i ] low[i] low[i]
无向图中
求割点(割顶)
- 割边判定法则:
x
x
x 是割点
⟺
\iff
⟺ 存在
x
x
x 的搜索树上的一个子节点
y
y
y 满足
d f n [ x ] ≤ l o w [ y ] \color{red}dfn[x]\le low[y] dfn[x]≤low[y] - 题目连接:P3388【模板】割点(割顶)
- 模板:
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
#define VI vector<int>
#define PII pair<int, int>
const db Pi = 3.141592653589793;
const int INF = 0x7fffffff;
const int N = 2e4 + 5;
const db eps = 1e-10;
int n, m;
int low[N], dfn[N];
int cut[N], tim, ans = 0;
vector<int> edge[N];
void tarjan(int now, int fa){
int son = 0;
low[now] = dfn[now] = ++tim;
for(auto next : edge[now]){
if(!dfn[next]){
son++;
tarjan(next, now);
low[now] = min(low[next], low[now]); //回溯更新
if(now != fa && low[next] >= dfn[now]) cut[now] = 1;
}
low[now] = min(low[now], dfn[next]);
}
if(now == fa && son >= 2) cut[now] = 1; //根节点情况
}
int main(){
cin >> n >> m;
rep(i, 1, m){
int x, y; cin >> x >> y;
edge[x].push_back(y), edge[y].push_back(x);
}
memset(cut, 0, sizeof(cut));
rep(i, 1, n){ //防止原图不连通
if(!dfn[i]){
tim = 0; //时间戳
tarjan(i, i); //将根节点的父亲设为自己
}
}
rep(i, 1, n) if(cut[i]) ans++;
cout << ans << endl;
rep(i, 1, n){
if(cut[i]) cout << i << " ";
}
}
求割边(桥)
- 割边判定法则:无向边
(
x
,
y
)
(x,y)
(x,y) 是桥
⟺
\iff
⟺ 存在
x
x
x 的搜索树上的一个子节点
y
y
y 满足
d f n [ x ] < l o w [ y ] \color{red}dfn[x]<low[y] dfn[x]<low[y] - 题目链接:P1656 炸铁路
- 模板:
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
#define VI vector<int>
#define PII pair<int, int>
const db Pi = 3.141592653589793;
const int INF = 0x7fffffff;
const int N = 1e5 + 5;
const db eps = 1e-10;
int n, m;
int low[N], dfn[N]; //low[i]表示经过i和父节点的连边在子树上可以(直接或间接)访问到的最小的时间戳
int tim, ans = 0;
vector<int> edge[N];
vector<PII> bridge;
void tarjan(int now, int fa){
low[now] = dfn[now] = ++tim;
for(auto next : edge[now]){
if(!dfn[next]){
tarjan(next, now);
low[now] = min(low[now], low[next]); //回溯更新
if(low[next] > dfn[now]) bridge.push_back({now, next});
}
//dfn[next] == 1
else if(next != fa) low[now] = min(low[now], dfn[next]); //如果访问到了不是父亲节点的节点,更新low的值
}
}
int main(){
cin >> n >> m;
rep(i, 1, m){
int x, y; cin >> x >> y;
edge[x].push_back(y), edge[y].push_back(x);
}
rep(i, 1, n){ //防止原图不连通
if(!dfn[i]){
tim = 0; //时间戳
tarjan(i, i); //将根节点的父亲设为自己
}
}
sort(bridge.begin(), bridge.end());
for(auto [a, b] : bridge){
cout << min(a, b) << " " << max(a, b) << endl;
}
}
有向图中
求SCC且缩点
算法思想:
- 找 l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x],即环的起点
- 存环
- 强连通分量(SCC)判别法则:若从
x
x
x 回溯前,有
l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x]
成立,则栈中从 x x x 到栈顶的所有节点构成一个SCC - 题目链接:P3387 【模板】缩点
- 模板:
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
#define VI vector<int>
#define PII pair<int, int>
const db Pi = 3.141592653589793;
const int INF = 0x7fffffff;
const int N = 1e4 + 5;
const db eps = 1e-10;
int n, m, a[N];
int dfn[N], low[N], vis[N], tim;
//scc[i]表示点i所在scc的编号,scnt表示强连通分量数量
//sum[i]表示编号为i的scc内的权值和
int scc[N], scnt = 0, sum[N];
int dp[N], ans;
VI edge[N], newedge[N];
stack<int> st;
void tarjan(int now, int fa){
low[now] = dfn[now] = ++tim;
st.push(now);
vis[now] = 1;
for(auto next : edge[now]){
if(!dfn[next]){
tarjan(next, now);
low[now] = min(low[now], low[next]); //更新
}
else if(vis[next]) low[now] = min(low[now], dfn[next]); //成环
}
if(low[now] == dfn[now]){ //找到环中的最小点,压栈
scnt++;
int top = 0;
while(now != top){ //从top(即x)到栈顶now
top = st.top(); st.pop();
scc[top] = scnt; //本题没用到
vis[top] = 0;
sum[scnt] += a[top];
}
}
}
int dfs(int now){ //DAG记忆化搜索
if(dp[now]) return dp[now];
dp[now] = sum[now];
int maxn = 0;
for(auto i : newedge[now]) maxn = max(maxn, dfs(i));
return dp[now] += maxn;
}
int main(){
// freopen("1.in","r",stdin);
cin >> n >> m;
rep(i, 1, n) cin >> a[i];
rep(i, 1, m){
int u, v; cin >> u >> v;
edge[u].push_back(v); //有向图
}
rep(i, 1, n){ //防止原图不连通
if(!dfn[i]){
tim = 0; //时间戳
tarjan(i, i); //将根节点的父亲设为自己
}
}
//建新图
rep(u, 1, n) for(auto v : edge[u]){
if(scc[u] != scc[v]){
newedge[scc[u]].push_back(scc[v]);
}
}
//统计,防止原图不连通
rep(i, 1, scnt){
if(!vis[i]) ans = max(ans, dfs(i));
}
cout << ans << endl;
}

本文提供了Tarjan算法在无向图和有向图中的应用模板,包括求割点(割顶)、割边(桥)及有向图的强连通分量(SCC),并详细解释了算法原理和实现细节。

1905

被折叠的 条评论
为什么被折叠?



