随笔 - 164  文章 - 0  评论 - 4  阅读 - 9882

强连通分量

强连通分量

dfs 森林

将原有的有向边按照搜索的情况分为四类:

  • 树边 (tree edge):访问节点走过的边
  • 返祖边 (back edge):指向祖先节点的边
  • 横叉边 (cross edge):右子树指向左子树的边
  • 前向边 (forward edge):指向子树中节点的边

返祖边和树边必定构成环,横叉边和树边可能构成环,前向边不影响强连通分量

tarjan算法

一遍 dfs 维护强连通分量相关信息
复杂度 O(n+m)

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 ;
}
};

例题

综合运用

利用强连通分量缩点

当强连通分量个数为 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

还得是洛谷评论区,orz!
什么时候婚姻是不安全的,以第一对夫妻为例,如果说,可以有下面这种情况成立,即男 1 找女 2,男 2 找女 3,男 3 找女 4, ...,男 k 找女 1 时,构成闭环,剩下如果还有夫妻则保持不变,此时婚姻是不安全的。如果说最终无法实现,说明婚姻就是安全的。
有没有种强连通分量的感觉,如果说一对夫妻均在一个 SCC 里面,则婚姻就是不安全的;反之安全(感觉没有说清楚,再看看题解想一想)

做法:抽象建图,对于夫妻关系连边女 -> 男,对于情人关系连边男 -> 女,在利用 tarjan 缩点,记录每个点属于哪一个 SCC。如果夫妻二人属于一个 SCC,则不安全;反之安全

Qiansui_code


模板题

判断整个图是否强连通 - 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 统计最长链
但是如果按照题意建图,需要 O(n2) 条边,故可以对于一行的边找一个代理,所有该行的点均和这个代理点连接,对于所有行所有列均如此操作,边数可以降到 O(n)
之后就是在求强连通分量时做 DP,注意一点,因为有代理点的存在,故在最终统计时代理点对最终答案不产生贡献
代码:Qiansui_code

posted on   Qiansui  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示