首页 > 编程语言 >Tarjan算法求SCC,缩点

Tarjan算法求SCC,缩点

时间:2024-03-12 20:57:58浏览次数:25  
标签:缩点 Tarjan int typedef SCC dfn low cnt

  • Tanjan算法可以在O(n + m)的时间内求出强连通分量,常数小,是个非常优秀的算法。
  • 算法实现过程:
    dfn[x]表示x的dfs生成树上的编号,代表着时间戳,low[x]表示从x结点出发,能够访问到最早的时间戳。
    <1>进入u时,盖上时间戳,结点入栈。
    <2>枚举该点的结点的时候,分为三种情况:
    (1)如果该点v没有访问过,那么就对v进行深搜,然后返回的时候更新low[u],
    因为u是v的父节点,v能到达的点,u肯定能够到达,所以能够更新;
    (2)若v已经访问过了,但是v点还是在栈中,那么说明v点是u的祖先结点或者是左子树的兄弟结点,也就是返祖边或者是横叉边,那么就用dfn[v]来更新low[u]。
    (3)若v已经访问过并且已经不在栈中,说明v已经搜索完毕了,那么就直接忽略就可以了。
    <3>离开u点时,记录SCC,只有遍历完所有的SCC时才可以出栈,更新low的意义就是防止有点提前出栈。所以只有当low[u] == dfn[u]的时候才可以更新当前连通分量。
点击查看代码
#include<bits/stdc++.h>                      
using namespace std;
#define fi first
#define se second
#define lc p<<1
#define rc p<<1|1
//#define int long long
#define vi vector<int>
#define vpi vector<pair<int,int>>
#define vvi vector<vector<int>>
typedef long long ll;
typedef tuple<int,int,int> tp;
typedef pair<int,int> PII;
typedef pair<ll,pair<ll,ll>> PIII;
typedef pair<ll,pair<ll,pair<ll,ll>>> pIIII;
const int N = 2e5 + 7;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
struct SCC {
    int n,cnt,idx;
    vi stk,instk;
    vi dfn,low,scc,siz;
    vvi g;
    SCC() {};
    SCC(int _n) {init(_n);}
    void init(int _n) {
        this -> n = _n;
        g.assign(_n + 1,vi());
        instk.resize(_n + 1);
        dfn.resize(_n + 1);
        low.resize(_n + 1);
        scc.resize(_n + 1);
        siz.resize(_n + 1);
        idx = cnt = 0;
    }
    void add(int u,int v) {
        g[u].emplace_back(v);
    }
    void tarjan(int u) {
        dfn[u] = low[u] = ++idx;
        stk.emplace_back(u),instk[u] = 1;
        for(auto v : g[u]) {
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u],low[v]);
            } else if(instk[v]) {
                low[u] = min(low[u],dfn[v]);
            }
        }

        if(dfn[u] == low[u]) {
            int y;++cnt;
            do {
                y = stk.back();instk[y] = 0;
                scc[y] = cnt;
                stk.pop_back();
                siz[cnt]++;
            } while(y != u);
        }
    }
    void work() {
        for(int i = 1; i <= n; i++) {
            if(!dfn[i]) tarjan(i);
        }
    }
};
void solve() {
}
signed main() {	
	std::ios::sync_with_stdio(0);
	std::cin.tie(0);
	int t = 1;
	//cin >> t;
	while(t--) {
		solve();
	}
    //system("pause");
	return 0;
}
/*
Do smth instead of nothing and stay organized
Don't get stuck on one approach
*/
  • 缩点:其实缩点就是把一个有向有环图变成DAG图,这样就可以做很多DAG图上的操作。缩点的逻辑就是把一个强连通分量算作是一个点,那么整张图里面就不存在环了。
  • 缩点的过程:
    <1>因为SCC是按dfs序更新的,所以1号点所在的SCC是最后一个强连通分量。所以我们就从第一个点开始枚举每个点所有的邻点,如果这个值所在的SCC和枚举的点所在的SCC不同,那么就说明这个点是两个SCC的交点,然后就直接连一条边就行了,因为我们的SCC都是已经编过号了,所以直接连边就可以了。重复了也没关系,如果有强迫症的话可以unique一下,没什么影响,因为交点的数量不会很多。
点击查看代码
#include<bits/stdc++.h>                      
using namespace std;
#define fi first
#define se second
#define lc p<<1
#define rc p<<1|1
#define int long long
#define vi vector<int>
#define vvi vector<vector<int>>
#define vpi vector<pair<int,int>>
typedef long long ll;
typedef tuple<int,int,int> tp;
typedef pair<long long,long long> PII;
typedef pair<ll,pair<ll,ll>> PIII;
typedef pair<ll,pair<ll,pair<ll,ll>>> pIIII;
const int N = 2e5 + 7;
const ll inf = 1e18;
const int mod = 998244353;
struct SCC {
    int n,cnt,idx;
    vi stk,instk;
    vi dfn,low,scc,siz;
    vvi g;
    SCC() {};
    SCC(int _n) {init(_n);}
    void init(int _n) {
        this -> n = _n;
        g.assign(_n + 1,vi());
        instk.resize(_n + 1);
        dfn.resize(_n + 1);
        low.resize(_n + 1);
        scc.resize(_n + 1);
        siz.resize(_n + 1);
        idx = cnt = 0;
    }
    void add(int u,int v) {
        g[u].emplace_back(v);
    }
    void tarjan(int u) {
        dfn[u] = low[u] = ++idx;
        stk.emplace_back(u),instk[u] = 1;
        for(auto v : g[u]) {
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u],low[v]);
            } else if(instk[v]) {
                low[u] = min(low[u],dfn[v]);
            }
        }

        if(dfn[u] == low[u]) {
            int y;++cnt;
            do {
                y = stk.back();instk[y] = 0;
                scc[y] = cnt;
                stk.pop_back();
                siz[cnt]++;
            } while(y != u);
        }
    }
    void work() {
        for(int i = 1; i <= n; i++) {
            if(!dfn[i]) tarjan(i);
        }
    }
};
void solve() {
    int n,m;cin >> n >> m;
    vi dp(n + 1),w(n + 1),nw(n + 1);
    vvi ng(n + 1,vi());
    SCC g(n);
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(int i = 1; i <= m; i++) {
        int u,v;cin >> u >> v;
        g.add(u,v);
    }
    g.work();
    for(int i = 1; i <= n; i++) {
        nw[g.scc[i]] += w[i];
        for(auto v : g.g[i]) {
            int a = g.scc[i],b = g.scc[v];
            if(a != b) {
                ng[a].push_back(b);
            }
        }
    }
    for(int i = g.cnt; i >= 1; i--) {
        if(dp[i] == 0) dp[i] = nw[i];
        for(auto v : ng[i]) {
            dp[v] = max(dp[v],dp[i] + nw[v]);
        }
    }
    int ans = -1e9;
    for(int i = 1; i <= g.cnt; i++) {
        ans = max(ans,dp[i]);
    }
    cout << ans << "\n";
}
signed main() {
	std::ios::sync_with_stdio(0);
	std::cin.tie(0);
	int t = 1;
	//cin >> t;
	while(t--) {
		solve();
	}
    //system("pause");
	return 0;
}
/*
Do smth instead of nothing and stay organized
Don't get stuck on one approach
*/

标签:缩点,Tarjan,int,typedef,SCC,dfn,low,cnt
From: https://www.cnblogs.com/Jerry114514/p/18069249

相关文章

  • tarjan板子整理
    缩点stack<int>q;voidtarjan(intu){ pre[u]=low[u]=++cnt; q.push(u); vis[u]=1; for(inti=head[u];i;i=nxt[i]) { inty=to[i]; if(!pre[y]) { tarjan(y); low[u]=min(low[u],low[y]); } elseif(vis[y]) { low[u]=min(low[u],pre[y]);......
  • Tarjan 全家桶
    部分定义摘自Alex_Wei的博客,如有侵权,请联系笔者删除。割点在无向图中,删去后使得连通分量数增加的结点称为割点。孤立点不是割点。非根节点\(u\)为割点当且仅当dfs树上存在一条树边\(u\rightarrowv\)使得\(\operatorname{low}_v\geq\operatorname{dfn}_u\)。根节......
  • Tarjan 算法
    part1:离线求\(lca\)将走过的点且未回溯的标记为\(1\),已回溯的标记为\(2\),未访问标记为\(0\)把对于一点\(x\)的询问\(y\)存进相应的\(vector\),当回溯时判断如果询问的点标记为\(2\),则\(lca\)为询问的点\(y\)到根的路径上最早标记为\(1\)的点但直接找复杂度不对......
  • 学习笔记——缩点
    前置知识:Tarjan求强连通分量一、什么是缩点缩点,就是把一张有向有环图上的一个个环缩成点,使其变成有向无环图。二、缩点的应用求最长路时常用。标准问法:给定一个有向图,每个点有一个权值,允许多次经过一条边或者一个点,重复经过的点,权值只计算一次。求最大权值若直接......
  • Dijkstra/Tarjan-关键边
    题目描述总统要回家,即从s走到t,会经过一些街道,每条街道都是单向的并且拥有权值。现在,为了让总统更好的回家,要对每一条街道进行操作:①如果该街道一定在最短路上,则输出“YES”。②如果该街道修理过后,该边所在的最短路可以取代原先的最短路,则输出“CANx”,x是修改该街道的最小花......
  • 强连通分量(SCC,Strongly Connected Components)学习笔记 & edited in 2024.01.31
    更新日志upd2024.01.31写好文章基本内容upd2024.01.31发表于洛谷upd2024.02.01同步发表于CSDNupd2024.02.01同步发表于博客园cnblogs强连通分量(SCC,StronglyConnectedComponents)定义强连通有向图(DAG)中若其中两点$x$,$y$能彼此到达(不一定是直接连边),称$x$和......
  • [ARC163D] Sum of SCC 题解
    题目链接点击打开链接题目解法好牛的性质!!!首先一个家喻户晓的性质是:竞赛图缩点之后是一条链考虑从这个上面拓展出一个更优美的性质:竞赛图的\(scc\)个数\(=\)把点集分成两个集合\(A,B\),满足\(\forallu\inA,v\inB\),\(u,v\)之间边的方向为\(u\tov\)的方案数\(-1\)......
  • tarjan 全家桶
    无向图的tarjan求边双连通分量的个数以及每个边双连通分量的大小以及每个边双连通分量包含哪些点。bridge数组表示一条边是不是桥。#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10,M=2e6+10;structedge{ intx,y,pre;}a[M*2];intalen,last[N];voidin......
  • 【板子】强连通分量(SCC)
    //强连通分量//lg2863求强连通分量的数量#include<bits/stdc++.h>usingnamespacestd;constintN=(int)2e4+4;intwhere[N];//这个点在哪个scc里intscccnt;intsccsize[N];intlow[N],dfn[N],idx;boolinstk[N];stack<int>stk;vector<int>e[N];intn,m;......
  • Tarjan
    Tarjan前置知识搜索树&搜索森林\(在一张连通图中,所有的节点以及发生递归的边共同构成一棵搜索树。如果这张图不连通,则构成搜索森林。\)\(\color{green}树边:\)\(dfs经过的边,dfs搜索树の边\)\(\color{yellow}返祖边:\)\(可以理解为返回去の边\)\(\color{red}横叉边:\)\(df......