[洛谷]P3388 【模板】割点(割顶) (#图论-Tarjan求割点)

26 篇文章 0 订阅
17 篇文章 0 订阅

题目背景

割点

题目描述

给出一个n个点,m条边的无向图,求图的割点。

输入输出格式

输入格式:

第一行输入n,m

下面mm行每行输入x,yx,y表示xx到yy有一条边

输出格:

第一行输出割点个数

第二行按照节点编号从小到大输出节点,用空格隔开

输入输出样例

输入样例#1

6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6

输出样例#1

1 
5

说明

对于全部数据,n \le 20000n≤20000,m \le 100000m≤100000

点的编号均大于0小于等于n。

tarjan图不一定联通。


思路

这题不能用邻接矩阵,邻接矩阵无论怎么优化,时间复杂度都为O(n^2),而邻接表的复杂度为O(n+m)。

一道割点的模板题,我这里是用的tarjan(其实我一开始真的不知道Tarjan能干那么多事情)算法的思想,但是这题和tarjan不完全相同。

邻接矩阵:

//这个只是割点的板子,没办法通过此题! 
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
int a[101][101],n,m,root;
int num[101],low[101],flag[101],ind,s;//low:存自己时间,num:能搜到的最早时间 
void dfs(int cur,int fa)//cur是当前顶点的编号,fa是父节点的编号 
{
	register int child(0),i;//child记录生成树中当前顶点cur的儿子个数 
	ind++;//时间戳+1 
	num[cur]=ind;//当前顶点cur的时间戳 
	low[cur]=ind;//当前顶点cur能够访问到最早顶点的时间戳,刚开始是自己 
	for(i=1;i<=n;i++)//枚举与当前顶点cur有边相连的顶点i 
	{
		if(a[cur][i]==1)
		{
			if(num[i]==0)//如果顶点i的时间戳为0,说明顶点i没被访问过,此时i是cur的儿子 
			{
				child++;//儿子+1 
				dfs(i,cur);//继续dfs 
				low[cur]=min(low[cur],low[i]);//更新当前顶点cur能访问到最早顶点的时间戳 
				if(cur!=root && low[i]>=num[cur])//如果当前不是根节点,则为割点
				{
					flag[cur]=1;
					s++;
				}
				if(cur==root && child==2)//如果是,且在生成树中根节点必须有2个儿子,那么这个点才是割点
				{
					flag[cur]=1;
					s++;
				}
			}
			else if(i!=fa)//如果顶点i被访问过,并且这个顶点不是当前顶点的父亲,说明i为cur的祖先,此时更新当前点cur能访问到最早顶点的时间戳 
			{
				low[cur]=min(low[cur],num[i]);
			}
		}
	}
	return;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j,k;
	cin>>n>>m;
	memset(a,0,sizeof a);
	for(i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		a[x][y]=a[y][x]=1;
	}
	root=1;
	dfs(1,root);//从1开始 
	
	cout<<s<<endl;//有多少个割点 
	for(i=1;i<=n;i++)
	{
		if(flag[i]==1)//输出是割点的 
		{
			cout<<i<<endl;
		}
	}
	return 0;
}
/*
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6

1
2
*/

邻接表:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
typedef struct
{
	int from,to;
	int nxt;
}lxydl;
lxydl a[200001];//邻接表存图,邻接矩阵不炸我吃屎 
int n,m,root(-1),len;
int head[100001],num[100001],low[100001],flag[100001],vis[100001],ind,s;//low:存自己时间,num:能搜到的最早时间
inline void add(int x,int y)
{
	a[++len].to=y;
	a[len].from=x;
	a[len].nxt=head[x];
	head[x]=len;
	a[++len].to=x;
	a[len].from=y;
	a[len].nxt=head[y];
	head[y]=len;
}
void dfs(int cur,int fa)//cur是当前顶点的编号,fa是父节点的编号 
{
	register int child(0),i;//child记录生成树中当前顶点cur的儿子个数 
	ind++;//时间戳+1
	vis[cur]=1;
	num[cur]=ind;//当前顶点cur的时间戳 
	low[cur]=ind;//当前顶点cur能够访问到最早顶点的时间戳,刚开始是自己 
	for(i=head[cur];i!=0;i=a[i].nxt)//查找与当前顶点cur有边相连的顶点 
	{
		int v=a[i].to;
		if(vis[v]==1 && v!=fa)//如果顶点i被访问过,并且这个顶点不是当前顶点的父亲,说明i为cur的祖先,此时更新当前点cur能访问到最早顶点的时间戳 
		{
			low[cur]=min(low[cur],num[v]);
		}
		else if(num[v]==0)//未到达就搜索,当前顶点作为v的父节点传递 
		{//如果顶点i的时间戳为0,说明顶点i没被访问过,此时i是cur的儿子 
			child++;//儿子+1
			dfs(v,cur);
			low[cur]=min(low[cur],low[v]);//更新当前顶点cur能访问到最早顶点的时间戳 
			if(fa!=root && low[v]>=num[cur])//如果当前不是根节点,则为割点
			{
				if(flag[cur]==0) s++;//记录割点数量 
				flag[cur]=1;//标记为割点 
			}
			if(fa==root && child>1)//如果是根节点,且在生成树中根节点必须有2个儿子,那么这个点才是割点
			{
				if(flag[cur]==0) s++;
				flag[cur]=1;
			}
		}
	}
	vis[cur]=2;//加了这个循环次数会变少 
	return;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j,k;
	cin>>n>>m;
	memset(a,0,sizeof a);
	for(i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
	}
	
	for(i=1;i<=n;i++)
	{
		if(!num[i])
		{
			dfs(i,root);
		}
	}
	
	cout<<s<<endl;//有多少个割点 
	for(i=1;i<=n;i++)
	{
		if(flag[i]==1)//输出是割点的 
		{
			cout<<i<<' ';
		}
	}
	cout<<endl;
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值