强连通分量
强连通分量
dfs 森林
将原有的有向边按照搜索的情况分为四类:
- 树边 (tree edge):访问节点走过的边
- 返祖边 (back edge):指向祖先节点的边
- 横叉边 (cross edge):右子树指向左子树的边
- 前向边 (forward edge):指向子树中节点的边
返祖边和树边必定构成环,横叉边和树边可能构成环,前向边不影响强连通分量
tarjan算法
一遍 dfs 维护强连通分量相关信息
复杂度
Kosaraju算法
DAG 出栈顺序是反图的拓扑序
有向图 SCC 缩点成 DAG
最后一个出栈的点就是反图源点
故,tarjan 的 scc 编号是反序的拓扑序
对原图和反图均做一次 dfs 求得强连通分量
相关资料
https://www.cnblogs.com/dx123/p/16320476.html
oi wiki - scc
板子 - tarjan
struct SCC{//Strongly Connected Components int n; int idx, cnt; vector<int> stk; vector<int> dfn, low, bel; vector<vector<int>> scc, e; SCC(){} SCC(int x) : n(x){ e.assign(n + 1, {}); dfn.assign(n + 1, 0); low.assign(n + 1, 0); bel.assign(n + 1, 0); stk.clear(); scc.clear(); idx = cnt = 0; } void add_edge(int u, int v){ e[u].push_back(v); return ; } void tarjan(int u){ dfn[u] = low[u] = ++ idx; stk.push_back(u); for(auto v : e[u]){ if(!dfn[v]){ tarjan(v); low[u] = min(low[u], low[v]); }else if(bel[v] == 0){ low[u] = min(low[u], dfn[v]); } } if(dfn[u] == low[u]){ ++ cnt; vector<int> t; int v; do{ v = stk.back(); t.push_back(v); bel[v] = cnt; stk.pop_back(); }while(v != u); scc.push_back(t); } return ; } void work(){ for(int i = 1; i <= n; ++ i){ if(!dfn[i]) tarjan(i); } return ; } };
例题
综合运用
-
缩点最大链点权和 - 洛谷 P3387 【模板】缩点
代码见提交
利用强连通分量缩点
当强连通分量个数为 1 时(需特判!!!)
子任务 A:1
子任务 B:0
因为此时整个图是强连通的
当强连通分量个数为 1 时
子任务 A:答案即为入度为 0 的点的个数
子任务 B:答案即为 max(入度为 0 的点数,出度为 0 的点数)。
因为想要将多个 DAG 变成一个 SCC,则最划算的就是将无出度点连向无入度点,为了连接尽可能少的边,从 0 出度点开始走,依次走过 0 入度点、0出度点、0 入度点、0出度点、... 直至某种点数不够,则将其连至已有的另一种点,剩下的也是如此。这样,就可以得到一个 SCC,所需加边数即为 max(入度为 0 的点数,出度为 0 的点数)
Qiansui_code
- 判给定 n 对点是否属于一个 SCC 洛谷 P1407 [国家集训队] 稳定婚姻
还得是洛谷评论区,orz!
什么时候婚姻是不安全的,以第一对夫妻为例,如果说,可以有下面这种情况成立,即男 1 找女 2,男 2 找女 3,男 3 找女 4, ...,男 k 找女 1 时,构成闭环,剩下如果还有夫妻则保持不变,此时婚姻是不安全的。如果说最终无法实现,说明婚姻就是安全的。
有没有种强连通分量的感觉,如果说一对夫妻均在一个 SCC 里面,则婚姻就是不安全的;反之安全(感觉没有说清楚,再看看题解想一想)
做法:抽象建图,对于夫妻关系连边女 -> 男,对于情人关系连边男 -> 女,在利用 tarjan 缩点,记录每个点属于哪一个 SCC。如果夫妻二人属于一个 SCC,则不安全;反之安全
模板题
判断整个图是否强连通 - hdu 1269 迷宫城堡
判断强连通分量是否只有一个即可
求强连通分量个数 - 洛谷 P2863 [USACO06JAN] The Cow Prom S
代码 - tarjan
//>>>Qiansui #include<bits/stdc++.h> #define ll long long #define ull unsigned long long #define mem(x,y) memset(x, y, sizeof(x)) #define debug(x) cout << #x << " = " << x << '\n' #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n' //#define int long long using namespace std; typedef pair<int, int> pii; typedef pair<ll, ll> pll; typedef pair<ull, ull> pull; typedef pair<double, double> pdd; /* 模板题 */ const int maxm = 1e4 + 5, inf = 0x3f3f3f3f, mod = 998244353; int n, m; vector<int> e[maxm]; int dfn[maxm], low[maxm], tot = 0; bool instk[maxm]; stack<int> stk; vector<vector<int>> scc; void tarjan(int u){ dfn[u] = low[u] = ++ tot; instk[u] = true; stk.push(u); for(auto v : e[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]){ vector<int> c; int v; do{ v = stk.top(); c.push_back(v); instk[v] = false; stk.pop(); }while(v != u); scc.push_back(c); } return ; } void solve(){ cin >> n >> m; for(int i = 0; i < m; ++ i){ int u, v; cin >> u >> v; e[u].push_back(v); } for(int i = 1; i <= n; ++ i){ if(!dfn[i]) tarjan(i); } int ans = 0; for(auto c : scc){ if(c.size() > 1) ++ ans; } cout << ans << '\n'; return ; } signed main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int _ = 1; // cin >> _; while(_ --){ solve(); } return 0; }
强连通分量缩点 - 洛谷 P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
题意
给定有向图,判断图中有多少个点满足图中剩余的点均可达该点
思路
利用强连通分量缩点,再判断是否仅有一个连通块出度为 0
强连通分量缩点,整图变为 DAG,再看是否存在唯一汇点
代码
//>>>Qiansui #include<bits/stdc++.h> #define ll long long #define ull unsigned long long #define mem(x,y) memset(x, y, sizeof(x)) #define debug(x) cout << #x << " = " << x << '\n' #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n' //#define int long long using namespace std; typedef pair<int, int> pii; typedef pair<ll, ll> pll; typedef pair<ull, ull> pull; typedef pair<double, double> pdd; /* */ const int maxm = 1e4 + 5, inf = 0x3f3f3f3f, mod = 998244353; int n, m; vector<int> e[maxm]; int dfn[maxm], low[maxm], bel[maxm], sz[maxm], cnt = 0, idx = 0; bool instk[maxm]; stack<int> stk; void tarjan(int u){ dfn[u] = low[u] = ++ idx; instk[u] = true; stk.push(u); for(auto v : e[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]){ ++ cnt; while(true){ ++ sz[cnt]; int v = stk.top(); bel[v] = cnt; instk[v] = false; stk.pop(); if(v == u) break; } } return ; } void solve(){ cin >> n >> m; for(int i = 0; i < m; ++ i){ int u, v; cin >> u >> v; e[u].push_back(v); } for(int i = 1; i <= n; ++ i){ if(!dfn[i]) tarjan(i); } int cnts = 0, cntv = 0; vector<int> outd(n + 1, 0); for(int u = 1; u <= n; ++ u){ for(auto v : e[u]){ if(bel[u] != bel[v]) ++ outd[bel[u]]; } } for(int i = 1; i <= cnt; ++ i){ if(outd[i] == 0){ ++ cnts; cntv += sz[i]; } } if(cnts > 1) cout << "0\n"; else cout << cntv << '\n'; return ; } signed main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int _ = 1; // cin >> _; while(_ --){ solve(); } return 0; }
SCC + DP - 洛谷 P2272 [ZJOI2007] 最大半连通子图
tarjan 的 scc 编号是反序的拓扑序!
利用 SCC 缩点,再在新的 DAG 上跑 DP(简单DP,找最长路的长度和条数)
每一个点都记下自己所能抵达的最长链的长度和方案数
代码:Qiansui_code
tarjan + DP - 洛谷 P3627 [APIO2009] 抢掠计划
利用 tarjan 将每一个强连通分量分别考虑,再做 DP
仅需要从起点开始跑 tarjan 即可
DP 初值为负的足够大,这样判断当前强连通分量中是否存在酒吧即判断当前 DP 值是否需要赋 0
代码:Qiansui_code
tarjan + DP 洛谷 P2403 [SDOI2010] 所驼门王的宝藏
DP 思路依旧是 SCC 缩成 DAG,在 DAG 上跑 DP 统计最长链
但是如果按照题意建图,需要
之后就是在求强连通分量时做 DP,注意一点,因为有代理点的存在,故在最终统计时代理点对最终答案不产生贡献
代码:Qiansui_code
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17648644.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】