Tarjan算法求割点、割边、SCC(缩点)

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

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且缩点

算法思想:

  1. l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x],即环的起点
  2. 存环
  • 强连通分量(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值