本蒟蒻一下tarjan算法,然后就不知死活的觉得自己应该都会了,然后就在割点上卡了几天捂脸,来发一篇题解
其实,Tarjan算法的精髓是dfn和low
dfn[i]就是时间戳,即在什么时刻搜索到了点i
low[i]则是i点能回溯到的dfn最小的祖先
low[a]=min(low[a],dfn[p]);
ps:
给的图不一定是连通图(卡的我啊啊啊)
链式前向星开边要2倍(这个应该都知道吧)
#include <algorithm>
#include <iostream>
#include <cstdio>
#define FOR(i,n,m) for(int i=n;i<=m;++i)
#define min(a,b) (a<b?a:b)
#define re register
#define gc getchar()
using namespace std;
const int N=100010;
inline int read() {
re int x(0),f(1);
re char ch=gc;
while(ch<'0'||ch>'9') {
if(ch=='-') f=-1;
ch=gc;
}
while(ch>='0'&&ch<='9') {
x=(x<<1)+(x<<3)+(ch^48);
ch=gc;
}
return x*f;
}
//前向星
struct node {
int next,to;
} e[N<<1];
int h[N<<1],n,m,cnt;
#define add(u,v) e[++cnt].next=h[u],e[cnt].to=v,h[u]=cnt;
#define QXX(u) for(int i=h[u],v;v=e[i].to,i;i=e[i].next)
//上面都没什么用
//tarjan
int dfn[N],low[N],vis[N],s[N],tot,top;
/*
dfn :时间戳
low :i及i的子树中所有结点能到达的结点中dfn最小的结点的时间戳
*/
void tarjan(int u,int fa) {
// cout<<u<<" ";
int son=0;//以x为根的子树0的个数
dfn[u]=low[u]=++tot;
QXX(u) {
// cout<<v<<" ";
if(!dfn[v]) {
tarjin(v,u);
low[u]=min(low[u],low[v]);
//更新当前节点的low值
if(u==fa) ++son;
if(u!=fa&&low[v]>=dfn[u]) vis[u]=1;
//如果一个点不是根,并且它的儿子在不经过它的情况下无法回到它的父亲,那么它也是割点
}
else if(v!=fa) low[u]=min(low[u],dfn[v]);
//如果一个点被搜索过了并且是x的儿子或者是它的父亲,就可以直接回溯
}
if(u==fa&&son>=2) vis[u]=1;
//如果一个点是根并且有多于两个子树,就是割点
}
void work() {
scanf("%d%d",&n,&m);
FOR(i,1,m) {
int x,y;
x=read(),y=read();
add(x,y) add(y,x);
}
FOR(i,1,n) if(!dfn[i]) tarjin(i,i);
int Tot=0,ans[N];
FOR(i,1,n) if(vis[i]) ans[++Tot]=i;
cout<<Tot<<endl;
FOR(i,1,Tot) cout<<ans[i]<<" ";
}
int main() {
work();
return 0;
}