Tarjan小结-割点

引子

割点的定义:若从无向图中删除节点x以及所有与x关联的边之后,图将被分为两个或两个以上的不相连的部分,那么称作点x为图的一个割点。

可以形象化地理解为图的枢纽,没有这些枢纽,图就会解体。

本题中我们需要找到并输出这些割点。

如果硬要把无向图看成有向图,再硬把有向图看成加入了横叉返祖边的树,那么树根肯定是割点。

回归最早引入的dfn与low,给出两点:待判定点x与其连接的点i。如果dfn[x]<=low[i] 则在深搜中xi为父子关系,断掉x一定导致i与x父亲不联通。 反之说明i可以通过一些特殊边提前于他的父亲被遍历,此时断掉x不会对i造成联通性的影响。

考虑到是无向图,我们开一个变量fa表示访问到u的上一个节点,如果打算访问的点已经被访问过且不是点u的父亲说明有返祖边,更新low[u]。

特别地,如果前文提到的根节点只有一个儿子那么根就不是割点,记录儿子数量特判即可。

事实上在代码实现中,对于dfn[x]>dfn[i]的情况可以不把i当作x的儿子,并且关于树根的结论可以下放到每个点,每个满足dfn[x]<=low[i]的,不为树根的点都是割点,如果是树根,查看儿子个数即可。

int tim,root;
int dfn[MAXN],low[MAXN],cut[MAXN];//cut[i]表示点i是否为割点
void tarjan(int u){
	dfn[u]=low[u]=++tim;
	int son=0;
	for(int i=0;i<edge[u].size();i++){
		int v=edge[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]){
				son++;
				if(u!=root||son>1)cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

...
for(root=1;root<=n;root++)if(!dfn[root])tarjan(root);//只要保证联通,任意一个点都可以是树根。

例题

在本题中,割点与一般点的处理规则完全不同。

一般的点由于在断边之后其余的点都能联通,所以该点的答案为2*(n-1)。

如果改点为割点,那么在刚才的基础上,对分裂得到的k个部分两两配对(i,j),每一对都会导致i与j的每个点都相互不联通,且每个点都与割点相互不联通,可以顺带着统计。

我们在最早遍历点i时统计出以i为根的子树的大小siz[i],这样如果i是割点,那么i的每个儿子j所代表的siz[j]个点与其余n-siz[j]个点互相不联通,对ans[i]累加,并统计i的全部儿子个数,最后i的全部儿子与i的全部祖先不联通,累加。

#include<bits/stdc++.h>
#define int long long
#define MAXN 100001
using namespace std;
int n,m;
vector<int>edge[MAXN];
int tim,siz[MAXN],ans[MAXN];
int dfn[MAXN],low[MAXN],cut[MAXN],root;
void tarjan(int u){
	dfn[u]=low[u]=++tim;
	int child=0,sum=0;
	siz[u]=1;
	for(int i=0;i<edge[u].size();i++){
		int v=edge[u][i];
		if(!dfn[v]){
			tarjan(v);
			siz[u]+=siz[v];
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]){
				ans[u]+=(n-siz[v])*siz[v];
				sum+=siz[v];
				child++;
				if(u!=root||child>1)cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(!cut[u])ans[u]=2*(n-1);
	else ans[u]+=((n-sum-1)*(sum+1)+n-1);
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%lld%lld",&u,&v);
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	for(root=1;root<=n;root++)if(!dfn[root])tarjan(root);
	for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	return 0;
}

其主要与之后的桥一起跑点双变双解决一些颜色较深的题

posted @ 2024-02-20 19:31  RVm1eL_o6II  阅读(1)  评论(0编辑  收藏  举报