洛谷 P3388 【模板】割点(割顶) 根+非根+dfn[]+low[]+不一样的Tarjan算法

洛谷  P3388 【模板】割点(割顶)  根+非根+dfn[]+low[]+不一样的Tarjan算法

Tarjan算法,详见https://blog.csdn.net/mrcrack/article/details/103806804

在线测评地址https://www.luogu.com.cn/problemnew/solution/P3388

 

割点的概念

在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点(cut vertex / articulation point)。

例如,在下图中,0、3是割点,因为将0和3中任意一个去掉之后,图就不再连通。如果去掉0,则图被分成1、2和3、4两个连通分量;如果去掉3,则图被分成0、1、2和4两个连通分量。

怎么求割点

Tarjan算法

可以使用Tarjan算法求割点(注意,还有一个求连通分量的算法也叫Tarjan算法,与此算法类似)。(Tarjan,全名Robert Tarjan,美国计算机科学家。)

首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。

对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。

但这里也出现一个问题:怎么计算low[u]。

假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。

有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);

如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。

//以上摘自割点(Tarjan算法)

上图对应输入输出数据为

Input:
8 9
1 2
2 3
3 4
4 5
5 7
5 6
5 3
1 8
Output:
4
1 2 3 5

采用邻接表的方式读取上述数据,若用low[u]=min(low[u],low[v])(本该写为low[u]=min(low[u],dfn[v]);).输出结果为

3
1 2 3

显然错误。

要点,割点是根,非根的判定,非根需注意dfn[],low[]的区分,建议结合具体实例进行编码。

//提交WA,排查,发现int v,b,child=0;//此处错写成int v,b,child;
//样例通过,提交AC.2019-1-4

#include <stdio.h>
#define maxn 20010
#define maxm 100010
int n,m,head[maxn],tot;
int dfn[maxn],low[maxn],tag,cut[maxn];
struct node{
	int to,next;
}e[maxm<<1];
void add_edge(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
int min(int a,int b){
	return a<b?a:b;
}
void Tarjan(int u,int root){
	int v,b,child=0;//此处错写成int v,b,child;
	low[u]=dfn[u]=++tag;
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(!dfn[v]){
			Tarjan(v,root);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&u!=root)cut[u]=1;//非根节点
			if(u==root){//根节点
				child++;
				if(child>=2)cut[u]=1;
			}
		}
		low[u]=min(low[u],dfn[v]);//此处关键,也可写成else low[u]=min(low[u],dfn[v]);若写成low[u]=min(low[u],low[v]);错误
	}
}
int main(){
	int i,j,u,v,num=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		add_edge(u,v),add_edge(v,u);
	}
	for(i=1;i<=n;i++)
		if(!dfn[i])Tarjan(i,i);
	for(i=1;i<=n;i++)
		if(cut[i])num++;
	printf("%d\n",num);
	for(i=1;i<=n;i++)
		if(cut[i])printf("%d ",i);
	return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值