tarjan算法的应用

kuangbin的连通图专题
强连通总结

无向图割点割边

知乎大佬的详细讲解
割点与桥
两者
割点:
需要单独判断根是否为割点,有至少2个child即为割点

low[to] >= dfn[now]

割边

low[to] > dfn[now]  // 不用考虑根节点

tarjan求割点

P3388 【模板】割点(割顶)
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e4 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
vector <int> e[maxn];
int dfn[maxn], low[maxn];
int cut[maxn];//表示某点是否是割点
int dcnt;//全局变量记录dfs序

void tarjan(int now, int r){//记录当前节点、树的根节点
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	int c = 0;//记录该节点的孩子数
	for(auto to : e[now])
	{
		if(!dfn[to])// 没有访问过 
		{
			++c;
			tarjan(to, r);
			low[now] = min(low[now], low[to]);
			if(low[to] >= dfn[now] && now != r) cut[now] = 1;
		}
		else low[now] = min(low[now], dfn[to]);
	}
	if(c >= 2 && now == r) cut[now] = 1;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i, i);
	int cnt = 0;
	for(int i = 1; i <= n; ++i) if(cut[i]) ++cnt;
	cout << cnt << endl;
	for(int i = 1; i <= n; ++i) if(cut[i])
		cout << i << " ";
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

割点所割连通分量数

对于根节点,它的孩子数就是所割的连通分量数
对于非根节点,它所割的连通分量数为其满足条件 l o w [ t o ] > = d f n [ n o w ] low[to] >= dfn[now] low[to]>=dfn[now] 孩 子 数 + 1 孩子数+1 +1,因为其与父节点的连通,也会因为割点的去除而失去
code:

void tarjan(int now, int r){//记录当前节点、树的根节点
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	int c = 0;//记录该节点的孩子数
	for(auto to : e[now])
	{
		if(!dfn[to])// 没有访问过 
		{
			++c;
			tarjan(to, r);
			low[now] = min(low[now], low[to]);
			if(low[to] >= dfn[now] && now != r) cut[now]++;
		}
		else low[now] = min(low[now], dfn[to]);
	}
	if(c >= 2 && now == r) cut[now] = c;
	if(cut[now] && now != r) cut[now]++;
}

例题:
Router Mesh
题意:
求删掉第 i i i 个点后的连通块个数
思路:
割点所割连通分量个数的板子只能求出删除割点后增加的连通分量个数
需要稍加修改

if(c >= 2 && now == r) cut[now] = c;
if(cut[now] && now != r) cut[now]++;

改为

if(now == r) cut[now] = c;// 根节点孩子只有一个也计
if(now != r) cut[now]++;// 非割点也计一个

这时 c u t [ i ] cut[i] cut[i] 数组表示删去节点 i i i 后增加的连通块个数( i i i 并不一定要是割点
设连通块个数为 c n t cnt cnt
删除节点 i i i 后的连通分量个数即为 c u t [ i ] + c n t − 1 cut[i]+cnt-1 cut[i]+cnt1
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 3e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
vector <int> e[maxn];
int dfn[maxn], low[maxn];
int cut[maxn];//表示某点是否是割点
int dcnt;//全局变量记录dfs序

void tarjan(int now, int r){//记录当前节点、树的根节点
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	int c = 0;//记录该节点的孩子数
	for(auto to : e[now])
	{
		if(!dfn[to])// 没有访问过 
		{
			++c;
			tarjan(to, r);
			low[now] = min(low[now], low[to]);
			if(low[to] >= dfn[now] && now != r) cut[now]++;
		}
		else low[now] = min(low[now], dfn[to]);
	}
	if(now == r) cut[now] = c;
	if(now != r) cut[now]++;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	int cnt = 0;
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i, i), ++cnt;
	for(int i = 1; i <= n; ++i)
		cout << cut[i] + cnt - 1 << " ";
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

割边模板题
P1656 炸铁路
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e4 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int head[maxn], cnt = 1;// 前向星编号初始为1,每个边的编号从2开始
struct Edge{
	int to, next;
}e[maxn];
int dfn[maxn], low[maxn];
int dcnt;//全局变量记录dfs序
vector <pair<int,int> > v;// 存割边

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now, int id){//记录当前节点、前向星编号
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	for(int i = head[now]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(!dfn[to])// 没有访问过 
		{
			tarjan(to, i);
			low[now] = min(low[now], low[to]);
			if(low[to] > dfn[now]) v.push_back({now, to});
		}
		else if(i != (id ^ 1)) low[now] = min(low[now], dfn[to]);
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i, 0);
	sort(all(v));
	for(auto x : v) cout << x.first << " " << x.second << endl;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

# 有向图的强连通分量 && 缩点

有向图强连通分量(scc):在有向图 G G G 中,如果两个顶点 v i , v j v_i,v_j vi,vj 间( v i > v j v_i>v_j vi>vj)有一条从 v i v_i vi v j v_j vj 的有向路径,同时还有一条从 v j v_j vj v i v_i vi 的有向路径,则称两个顶点强连通。如果有向图 G G G 的每两个顶点都强连通,称 G G G 是一个强连通图。有向图的极大强连通子图,称为强连通分量

T103440 【模板】缩点
思路:
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct Edge{
	int to, next;
}e[maxn];
int head[maxn], cnt = 1;
int dfn[maxn], low[maxn];
int dcnt;//全局变量记录dfs序
int stk[maxn], top;// 栈 
bool vis[maxn];//表示当前点是否在栈中
int c;//强连通分量的个数,其实也是编号
int scc_id[maxn];//表示节点i所在的强连通分量的编号
int scc_sum[maxn];// 表示编号为i的强连通分量的权值和 
int val[maxn];

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now){//记录当前节点 
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	stk[++top] = now;
	vis[now] = 1;
	for(int i = head[now]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(!dfn[to])// 没有访问过 
		{
			tarjan(to);
			low[now] = min(low[now], low[to]);
		}
		else if(vis[to])//如果遍历过并且在栈中
			low[now] = min(low[now], dfn[to]);
		//为什么一定要在栈中?
        //因为如果不在栈中说明它已经属于其他强连通分量了
        //而每一次出栈都会弹出完整的强连通分量,所以这个点肯定不会产生影响
	}
	if(dfn[now] == low[now]){//说明是一个代表点,也就是一整个强连通分量
		++c;
		int t;
		do{
			t = stk[top--], vis[t] = 0;
			scc_id[t] = c;
			scc_sum[c] += val[t];
		}while(t != now);
	}
}

void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> val[i];
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		add(x, y);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i);
	map <int, int> ma;
	for(int i = 1; i <= n; ++i){
		if(!ma[scc_id[i]]){
			ma[scc_id[i]] = 1;
			cout << scc_sum[scc_id[i]] << endl;
		}
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

387 【模板】缩点
题意:
在一张有向图中找一个点权值和最大的路径,允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
思路:
根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个强连通分量上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次,这其实就是提示你需要缩点
缩点完成后就变成了一张 D A G DAG DAG,拓扑排序 d p dp dp 求答案就好了
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to;
}e[maxn];
int head[2][maxn], cnt;
int dfn[maxn], low[maxn], dcnt;
bool vis[maxn];
int stk[maxn], top;
int scc_sum[maxn], scc_id[maxn], id;
int val[maxn];
int dis[maxn], in[maxn];

void add(int op, int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[op][x];
	head[op][x] = cnt;
}
void tarjan(int x){
	dfn[x] = low[x] = ++dcnt;
	vis[x] = 1;
	stk[++top] = x;
	for(int i = head[0][x]; i; i = e[i].next){
		int to = e[i].to;
		if(!dfn[to]){
			tarjan(to);low[x] = min(low[x], low[to]);
		}
		else if(vis[to]) low[x] = min(low[x], dfn[to]);
	}
	if(dfn[x] == low[x]){
		++id;int t;
		do{
			t = stk[top--];vis[t] = 0;scc_id[t] = id;scc_sum[id] += val[t];
		}while(t != x);
	}
}
void topsort(){
	queue <int> q;
	for(int i = 1; i <= id; ++i) if(!in[i]){
		dis[i] = scc_sum[i];
		q.push(i);
	}
	while(!q.empty()){
		int x = q.front();q.pop();
		for(int i = head[1][x]; i; i = e[i].next){
			int to = e[i].to;
			in[to]--;
			if(!in[to]) q.push(to);
			dis[to] = max(dis[to], dis[x] + scc_sum[to]);
		}
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> val[i];
	for(int i = 1, x, y; i <= m; ++i){
		cin >> x >> y;add(0, x, y);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i]) tarjan(i);
	for(int i = 1; i <= n; ++i){
		for(int j = head[0][i]; j; j = e[j].next){
			int to = e[j].to;
			if(scc_id[to] != scc_id[i]){
				add(1, scc_id[i], scc_id[to]);
				in[scc_id[to]]++;
			}
		}
	}
	topsort();
	int Max = 0;
	for(int i = 1; i <= id; ++i) Max = max(Max, dis[i]);
	cout << Max;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G
题意:
思路:
模板题
tarjan求强连通分量步骤
根据题意,如果有 s c c scc scc,意味着这个 s c c scc scc 里的牛都互相喜欢
我们可以先求出 s c c scc scc,然后把每一个 s c c scc scc 都看作一个点即缩点,这样整个图就变成了一个 D A G DAG DAG(有向无环图)
看有几个点出度为 0 0 0 ,如果大于一个点没有出边,就说明没有最受欢迎的牛,因为两个 s c c scc scc 里的牛无法相互喜欢
如果只有一个,那么该强联通分量的大小就是答案
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e4 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct Edge{
	int to, next;
}e[maxn];
int head[maxn], cnt = 1;
int dfn[maxn], low[maxn];
int dcnt;//全局变量记录dfs序
int stk[maxn], top;// 栈 
bool vis[maxn];//表示当前点是否在栈中
int c;//强连通分量的个数,其实也是编号
vector <int> scc[maxn];//记录了编号为 i的强连通分量中的所有结点
// 下边两个这个题用不着,作为模板记录
int scc_id[maxn];//表示节点i所在的强连通分量的编号
int scc_c[maxn];//表示编号为i的强连通分量的个数

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now){//记录当前节点
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	stk[++top] = now;
	vis[now] = 1;
	for(int i = head[now]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(!dfn[to])// 没有访问过 
		{
			tarjan(to);
			low[now] = min(low[now], low[to]);
		}
		else if(vis[to])//如果遍历过并且在栈中
			low[now] = min(low[now], dfn[to]);
		//为什么一定要在栈中?
        //因为如果不在栈中说明它已经属于其他强连通分量了
        //而每一次出栈都会弹出完整的强连通分量,所以这个点肯定不会产生影响
	}
	if(dfn[now] == low[now]){//说明是一个代表点,也就是一整个强连通分量
		++c;
		int t;
		do{
			t = stk[top--], vis[t] = 0;
			scc_id[t] = c;
			scc[c].push_back(t);
			scc_c[c]++;
		}while(t != now);
	}
}

int out[maxn];
int ans;

void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		add(x, y);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i);// 缩点
	for(int i = 1; i <= n; ++i)
		for(int j = head[i]; j; j = e[j].next)
		{
			int to = e[j].to;
			if(scc_id[i] != scc_id[to])
				out[scc_id[i]]++;// scc_id[i]的强连通分量指向scc_id[to]的强连通分量
		}
	for(int i = 1; i <= c; ++i){
		if(!out[i]){
			if(!ans) ans = i;//有一个出度为 0 的即是答案
			else {// 大于等于两个出度为0的ssc,必然不会有明星牛
				cout << "0\n";return;
			}
		}
	}
	cout << scc_c[ans];
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

无向图的双连通分量 && 缩点

概念
双连通分量又分点双连通分量边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。

无向图的极大点双连通子图就是 “ 点 双 连 通 分 量 ” ( v − D C C ) “点双连通分量”(v-DCC) (vDCC),极大边双连通子图就是 “ 边 双 连 通 分 量 ” ( e − D C C ) “边双连通分量”(e-DCC) (eDCC)

定理

一张无向连通图是点双连通图当且仅当 图的顶点数 < = 2 <=2 <=2 o r or or 图中任意两点都同时包含在至少一个简单环中。

一张无向连通图是边双连通图当且仅当任意一条边都包含在至少一个简单环中

成对变换
2 2 2 x o r xor xor 1 = 3 1=3 1=3 3 3 3 x o r xor xor 1 = 2 1=2 1=2
4 4 4 x o r xor xor 1 = 5 1=5 1=5 5 5 5 x o r xor xor 1 = 4 1=4 1=4

对于存储在链式前向星的无向图或者双向图,我们分别将 u u u v v v 的边和 v v v u u u 的边存储在数组中的 n n n n + 1 n+1 n+1 处(实际上大部分时间我们也都是这么做的,添加一条无向边就是 a d d ( u , v ) , a d d ( v , u ) add(u,v),add(v,u) add(u,v),add(v,u),这两个边是挨着的),这样当取出一条边时,想得到与它反向的边的信息,只需异或一下即可,非常的方便。

T103489 【模板】边双连通分量
题意
求图中 e − D C C e-DCC eDCC 的数量
(割边进阶版
思路:
先用Tarjan算法标记所有桥,然后对整张图dfs一遍(不访问桥边),划分出所有连通块。
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct Edge{
	int to, next;
}e[maxn];
int head[maxn], cnt = 1;// 因为要用成对变化,所以前向星编号初始化为1
int dfn[maxn], low[maxn];
int dcnt;//全局变量记录dfs序
bool bridge[maxn];// 标记割边 
int c[maxn];// DCC 的编号
int dcc;//DCC 的数量,且是用来编号的

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now, int id){//记录当前节点、前向星的编号 
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	for(int i = head[now]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(!dfn[to])// 没有访问过 
		{
			tarjan(to, i);
			low[now] = min(low[now], low[to]);
			if(low[to] > dfn[now])
				bridge[i] = bridge[i ^ 1] = 1;// 标记桥
		}
		else if(i != (id ^ 1)) low[now] = min(low[now], dfn[to]);
	}
}
void dfs(int x){
	c[x] = dcc;
	for(int i = head[x]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(c[to] || bridge[i]) continue;
		dfs(to);
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i, 0);
	for(int i = 1; i <= n; ++i) if(!c[i])
		++dcc, dfs(i);
	cout << dcc;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

e − D C C e-DCC eDCC 的缩点
思路:
我们把每一个 e − D C C e-DCC eDCC 都看成一个结点(只是看成结点),把所有桥边 ( x , y ) (x,y) (x,y) 看成连接编号为 c [ x ] c[x] c[x] c [ y ] c[y] c[y] 的两个 e − D C C e-DCC eDCC 间的边,这样我们就会得到一棵树或者森林(原图不连通)。并把 e − D C C e-DCC eDCC 缩点生成的树(森林)储存在另一个邻接表中(新开一个链式前向星来存树)。

	for(int i = 2; i <= cnt; ++i){
		int x = e[i].to, y = e[i ^ 1].to;// 取出这条边的两个顶点 
		if(c[x] == c[y]) continue;
		add_c(c[x], c[y]);// 在新链式前向星中加入桥边 
	}

T103492 【模板】点双连通分量
题意:
求图中 v − D C C v-DCC vDCC 的数量
(割点进阶版
思路:
v − D C C v-DCC vDCC 是一个很容易混淆的概念。
由于 v − D C C v-DCC vDCC 定义中的 “ 极 大 ” “极大” , 一个割点可能属于多个 v − D C C v-DCC vDCC
为了求出 v − D C C v-DCC vDCC ,我们需要在 T a r j a n Tarjan Tarjan 的过程中维护一个栈。
当一个点第一次被访问时,我们将它入栈。而当割点判定法则成立时,无论 x x x 是否为根,都要从栈顶不断弹出节点直到 y y y 节点被弹出,这些被弹出的节点包括 x x x 节点一起构成一个 v − D C C v-DCC vDCC
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct Edge{
	int to, next;
}e[maxn];
int head[maxn], cnt = 1;
int dfn[maxn], low[maxn];
int cut[maxn];//表示某点是否是割点
int dcnt;//全局变量记录dfs序
int stk[maxn], top;// 栈 
int c;// DCC 的编号
vector <int> dcc[maxn];//DCC 的数量,且是用来编号的

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now, int r){//记录当前节点、根节点 
	dfn[now] = low[now] = ++dcnt;//初始时,low[now]=dfn[now]
	stk[++top] = now;
	if(now == r && !head[now]){// 孤立点 
		dcc[++c].push_back(now);
		return;
	}
	int x = 0;
	for(int i = head[now]; i; i = e[i].next)
	{
		int to = e[i].to;
		if(!dfn[to])// 没有访问过 
		{
			++x;
			tarjan(to, r);
			low[now] = min(low[now], low[to]);
			if(low[to] >= dfn[now])
			{
				if(now != r) cut[now] = 1;
				++c;
				while(stk[top] != to) dcc[c].push_back(stk[top--]);
				dcc[c].push_back(stk[top--]);
				dcc[c].push_back(now);
			}
		}
		else low[now] = min(low[now], dfn[to]);
	}
	if(x > 1 && now == r) cut[now] = 1;
}

void work()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i){
		int x, y;cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i])
		tarjan(i, i);
	for(int i = 1; i <= c; ++i){
		for(auto d : dcc[i]) cout << d << " ";cout << endl;
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

v − D C C v-DCC vDCC 的缩点
不学了,繁繁的学习博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值