题目描述
给出一个n个点,m条边的无向图,求图的割点。
输入格式:
第一行输入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,m均为100000
tarjan 图不一定联通!!!
思路
求割点的模板题,说明几个要注意的地方:
对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。
- 判断是否是割点的条件是:
low[v]>=dfn[u]
- 一个点至少有一个子女,才能成为割点
- 关于tarjan算法,一直有一个很大的争议,就是low[u]=min(low[u],dfn[v]);
这句话,如果改成low[u]=min(low[u],low[v])就会wa掉,但是在求强连通分量时却没有问题
根据许多大佬的观点,我想提出自己的一点看法,在求强连通分量时,如果v已经在栈中,那么说明u,v一定在同一个强连通分量中,所以到最后low[u]=low[v]是必然的,提前更新也不会有问题,但是在求割点时,low的定义有了小小的变化,不再是最早能追溯到的祖先,(因为是个无向图)没有意义,应该是最早能绕到的割点,为什么用绕到,是因为是无向边,所以有另一条路可以走,如果把dfn[v]改掉就会上翻过头,可能翻进另一个环中,所以wa掉(转自洛谷)
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=100010*2;
int low[N],dfn[N],dfs_num;
int tot,son;
int subnets[N],vis[N],root,first[N];
struct edge
{
int v,next;
} e[N*10];
void init()
{
mem(first,-1);
tot=0;
}
void add(int u,int v)
{
e[tot].v=v;
e[tot].next=first[u];
first[u]=tot++;
}
void get_cut_point(int u)//求割点
{
if(u!=root)
subnets[u]++;
else son++;
}
void dfs(int u)
{
low[u]=dfn[u]=++dfs_num;
vis[u]=1;
for(int i=first[u]; ~i; i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
//求割点
if(low[v]>=dfn[u])
get_cut_point(u);//割点
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
}
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
init();
for(int i=1; i<=m; i++)
{
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1; i<=n; i++)
if(!dfn[i])
{
root=i;
son=0;
dfs(i);
if(son>1)
subnets[i]=1;
}
int cnt=0;
for(int i=1; i<=n; i++)
if(subnets[i])
cnt++;
printf("%d\n",cnt);
for(int i=1; i<=n; i++)
if(subnets[i])
printf("%d ",i);
return 0;
}
2018年01月22日重构代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 1000000
#define mem(a,b) memset(a,b,sizeof(a))
const int N=100000+7;
const int M=2*100000+20;
int dfn[N],low[N],times;
int root,son;
int n,m;
int first[N],tot,subnets[N];
struct edge
{
int v;
int next;
} e[M];
void add_edge(int u,int v)
{
e[tot].v=v;
e[tot].next=first[u];
first[u]=tot++;
}
void init()
{
mem(dfn,0);
mem(low,0);
mem(first,-1);
mem(subnets,0);
tot=0;
times=0;
}
void tarjan(int u)
{
low[u]=dfn[u]=++times;
for(int i=first[u]; ~i; i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
if(u==root)
son++;
else
subnets[u]++;
}
}
else
low[u]=min(low[u],dfn[v]);
}
}
int main()
{
int u,v;
init();
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
for(int i=1; i<=n; i++)
if(!dfn[i])
{
root=i;
son=0;
tarjan(i);
if(son>1)
subnets[i]=1;
}
int sum=0;
for(int i=1; i<=n; i++)
if(subnets[i])
sum++;
printf("%d\n",sum);
for(int i=1; i<=n; i++)
if(subnets[i])
printf("%d ",i);
return 0;
}