这是一道强连通的模板提,可能在某些方面还没有模板复杂,但对要学习强连通的人来说,这道题可以充分增加他们对强连通算法的认识。
题目是这样的(转自洛谷)
给出一个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]。(转自我自己的博客)
#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;
}
功德圆满~