tarjan求割点

首先分享一篇很不错的blog

博客链接

在tarjan求强连通分量时,有一个dfn数组和一个low数组,分别记录了dfs遍历树的次序,和通过不在搜索序列上的边能到达的最小的dfn的值,如果对于一个点 a 和它的子节点 b ,在遍历的时候如果子节点 b 没有办法通过一个不在搜索序列边到达更小的一个dfn的话(即不能到达比 a 的dfn小的位置)即,

low[b] >= dfn[a];

说明没有办法通过 b 不经过 a 访问到 a 之前的节点,也就说明 a 时一个割点

特别的

如果点 a 是搜索树的根节点(深度优先遍历的起点),则需要至少两个满足上述条件的值,如果多于1个点满足条件的话,显然不通过 a 点就不能访问的其他 a 的邻接点,因此 a 是割点,但是如果只有一个,就成了一个形如a -> b -> c的结构,显然把端点 a 去掉没有任何影响

例子

Input

5 5
1 2
2 3
1 3
3 4
4 5

Output 

3 4
2
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define mem(a) memset(a, 0, sizeof (a))
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int low[N], dfn[N], st[N], cut[N], v[N], top, n, x, num, m;
void pre_work(){
	for (int i = 0; i < N; i++)g[i].clear();
	top = num = 0;
	mem(low), mem(dfn), mem(st), mem(cut), mem(v);
}
void init(){
	for (int i = 0; i < m; i++){
		int a, b;
		scanf("%d %d", &a, &b);
		g[a].push_back(b);
		g[b].push_back(a); 
	}
}
int tarjan(int x){
	dfn[x] = low[x] = ++num;st[++top] = x;v[x] = 1;
	int cnt = 0;
	for (vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++){
		int t = *it;
		if(dfn[t] == 0){
			tarjan(t);
			low[x] = min(low[x], low[t]);
			if(dfn[x] <= low[t])cnt++;
		}
		else if(v[t]){
			low[x] = min(low[x], dfn[t]);
		}
	}
	if(dfn[x] == low[x]){
		while (top > 0){
			int Temp = st[top--];
			v[Temp] = 0;
			if(Temp == x)break;
		}
	}
	if(cnt >= 1)cut[x] = 1;
	//计算过程中每个点都按照不是根的情况计算
	//最后返回根节点的满足dfn[x] <= low[t]的个数
	//如果大于等于两个,cut[root] = 1;
	//否在 cut[root] = 0; 
	return cnt;
}
void solve(){
	pre_work();
	init();
	for (int i = 1; i <= n; i++){
		if(!dfn[i]){
			int temp = tarjan(i);
			if(temp < 2)cut[i] = 0;
		}
	}
	int res = 0;
	for (int i = 1; i <= n; i++)if(cut[i])printf("%d ", i), ++res;
	puts("");
	printf("%d\n", res);
}
int main()
{
	while (~scanf("%d %d", &n, &m) && (n + m)){
		solve();
	}
	return 0;
}

 

posted @ 2019-08-24 21:32  correct  阅读(125)  评论(0)    收藏  举报