#include
#include
using namespace std;
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
const int N=1e5+10;
struct Edge{
int v,nx;
}edge[N<<1];
int dfn[N],low[N],n,m,head[N],tot,num,root;
void addedge(int u,int v)
{
edge[++tot].v=v;
edge[tot].nx=head[u];
head[u]=tot;
}
bool cut[N];
void tarjan(int u)
{
dfn[u]=low[u]=++num;
int flag=0;
for(int i=head[u];i;i=edge[i].nx)
{
int v=edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
flag++;
if(u!=root||flag>1)cut[u]=true;
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int main()
{
scanf("%d%d",&n,&m);
tot=1;
int u,v;
_rep(i,1,m){scanf("%d%d",&u,&v);if(u!=v)addedge(u,v),addedge(v,u);}
_rep(i,1,n)if(!dfn[i])root=i,tarjan(i);
_rep(i,1,n)if(cut[i])printf("%d ",i);
puts("are cut-vertexes");
return 0;
}
为了求出“点双连通分量”,需要在Tarjan算法的过程中维护一个栈,并按照如下方法维护栈中的元素:
1.当一个节点第一次被访问时,把该节点入栈。
2.当判定是割点时,无论x是否为根都要
(1)从栈顶不断弹出节点,直至节点y被弹出。
(2)刚才弹出的所有节点与节点x一起构成一个v-DCC。
void tarjan(int x)
{
dfn[x]=low[x]=++num;
stack[++top]=x;
if(x==root&&head[x]==0)//孤立点
{
dcc[++cnt].push_back(x);
return;
}
int flag=0;
for(int i=head[x];~i;i=edge[i].nx)
{
int v=edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
flag++;
if(u!=root||flag>1)cut[u]=true;
cnt++;
int z;
do{
z=stack[top--];
dcc[cnt].push_back(z);
}while(z!=v);
dcc[cnt].push_back(u);
}
}
else low[u]=min(low[u],dfn[v]);
}
}
//以下代码片段加在求割点程序的main函数中
_rep(i,1,cnt)
{
printf("e-DCC #%d",i);
for(int j=0;jprintf(" %d",dcc[i][j]);
puts("");
}
v-DCC的缩点
//给每个割点一个新的编号
num=cnt;
_rep(i,1,n)if(cut[i])new_id[i]=++num;
//建新图,从每个v-DCC到它包含的所有割点连边
tc=1;
_rep(i,1,cnt)_rep(j,0,dcc[i].size())
{
int u=dcc[i][j];
if(cut[u])add_c(i,new_id[u]),add_c(new_id[u],i);
else c[x]=i;//除割点外,其他点仅属于1个v-DCC
}
printf("缩点之后的森林,点数%d,边数%d\n",num,tc/2);
printf("编号1~%d的为原图的v-DCC,编号>%d的为原图割点\n",cnt,cnt);
for(int i=2;i2)printf("%d %d\n",vc[i^1],vc[i]);