割点-tarjan

这是一道强连通的模板提,可能在某些方面还没有模板复杂,但对要学习强连通的人来说,这道题可以充分增加他们对强连通算法的认识。

题目是这样的(转自洛谷
给出一个n个点,mm条边的无向图,求图的割点。

输入格式
第一行输入n,m
下面m行每行输入x,y表示x到y有一条边

输出格式
第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
输入输出样例
输入 #1
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
输出 #1
1
5
说明/提示
对于全部数据 n≤20000 m≤100000
点的编号均大于0小于等于n。
tarjan图不一定联通。

要求的是这个图的割点诶,割点的看起来很好理解,但为什么自己上手写代码的时候就不能让计算机快速辨别了呢?嘿,别急,计算机可没有人聪明,你要让找出割点的话,首先你得告诉它割点的本质才行。那割点的本质是什么呢?不就是去掉它就会增加图的联通分量的吗?那我们一个一个试不就完了?那得等到猴年马月呀,毕竟人家20000个点的数据在那等着呢。碰到这种情况,一定是你的本质还挖掘得不够,如果一个点是割点的话,那是不是从这一边经过割点到另一边,那么另一边的所有点都是从割点过来的,换言之,如果这个点是割点的话,那么它必然可以吧一个深度遍历的树分为两个部分,那么怎么去辨别这一性质呢?这个时候tarjan的伟大之处就来了。
那么tarjan又具体是怎么实现的呢?首先,我们需要明白一个时间戳的概念。命名一个记录时间点的变量idx,每dfs找到一个点,就将该点的时间戳赋值为时间节点idx,然后用idx++来模拟“时间流逝”,这样就可以记录下每一个节点被遍历到的次序了。

既然时间记录已经完成了,那么这个时间戳又有什么用呢?使用两个数组dfn和low。dfn[u]表示dfs时达到顶点u的次序号(时间戳),low[u]表示以u为根节点的dfs树中次序号最小的顶点的次序号(也就是说low中储存的是当前这个节点所能到达的时间戳最小的点)。除了两个int数组外,还有两个布尔数组instack和vis,前者用来存储该节点是否还在栈中,而后者用来存储这个节点是否有没有访问过(在后面的学习中清务必明确二者的区别)。

在经过一个点low值的一系列更新后,如果这个点找不到dfn比它自己还小的点的时候,也就是当dfn[u]=low[u]时,以u为根的搜索子树上所有节点是一个强连通分量。 先将顶点u入栈,dfn[u]=low[u]=++idx,扫描u能到达的顶点v,如果v没有被访问过,则dfs(v),low[u]=min(low[u],low[v]),如果v在栈里,low[u]=min(low[u],dfn[v]),扫描完v以后,如果dfn[u]=low[u]。(转自我自己的博客
tarjan大佬

#include <stdio.h>
#include <queue>
#include <iostream>
#include <vector>

using namespace std;

int n,m,to[1002345],from[1002345],head[1002345],next[1002345],root;
int dfn[900000]={0},low[900000]={0},flag[900000]={0},t=0,ans,idx;
  
void adde(int x,int y,int i)
{
	from[i]=x;	to[i]=y;
	next[i]=head[x];
	head[x]=i;
}


void tarjan(int n,int r)
{
	int child=0;
	dfn[n]=low[n]=++idx;


	for(int i=head[n];i;i=next[i])
	{
		int t=to[i];
		if(!dfn[t])
		{
			tarjan(t,r);
			low[n]=min(low[t],low[n]);
			if (low[t]>=dfn[n]&&n!=r) {
		//		ans++;
				flag[n]=1;
			}
			if(n==r)	child++;
		}

		
			low[n]=min(low[n],dfn[t]);		
	}
	
	if(n==r&&child>=2)
	{
//		ans++;	
		flag[n]=1;
	}

	
	
}

int main(void)
{
//	freopen("ts.in","r",stdin);
//	freopen("point.out","w",stdout);	
 //   int i,j,x,y;
    scanf("%d %d",&n,&m);

	int x,y;
    for(int i=1;i<=m*2;i++)   //输入无向图的每条边 
   	{
	 	scanf("%d %d",&x,&y);  		
   		adde(x,y,i++);
   		adde(y,x,i);
   	}

   for(int i=1;i<=n;i++)
   	if(!dfn[i])
   		tarjan(i,i);
   	for(int i=1;i<=n;i++)
	   if(flag[i])	ans++;	
	 printf("%d\n",ans); 
	for(int i=1;i<=n;i++)  //输出割点 
		if(flag[i])
			printf("%d ",i); 
//	fclose(stdin);
//	fclose(stdout);       
    return 0;
}

功德圆满~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值