【模板】边双连通分量
World_Creater · · 题解
修复了英文与中文间的空格
orz 题解区的神犇已经将桥和边双的基本做法讲明白了,那本蒟蒻就写一种别的做法吧。
利用栈存储答案
这种做法与点双 十分类似,都是将遍历到的点依次加入栈,并在找到一个桥时,将栈中的存储的节点弹出作为一个边双的答案,仅在细节处理时有些变化。
- 记录答案时(找到桥时)不再记录当前节点。
这个很显然吧,毕竟这条桥就是用来区分边双的。 - 若图中不存在桥时
我们在求点双时,暴力地将割点的条件直接修改为low[v]>=dfn[u]而省略根节点的判断。这样我们可以将剩余的节点与根节点一起放入一个点双。
但很显然,如果图中不存在桥,就不可能使条件low[v]>dfn[u]成立
我们考虑在每一次搜索前,建立一个虚拟源点指向原来的根(单向双向均可,因为正常的Tarjan本来就不要求走回头路),让这个虚拟节点成为新的根。这样,原先我们新加的虚拟边就成为了虚拟桥。这样就使图中至少存在一个桥。也可以解决独立节点的问题。
下面是代码
#include<bits/stdc++.h>
using namespace std;
int n,m,nxt[4100005],head[2000005],k=1,cnt,go[4100005],dfn[2000005],low[2000005],ans;
vector<int> dcc[500005];
void add(int x,int y)
{
nxt[++k]=head[x];
head[x]=k;
go[k]=y;
}
stack<int>sta;
void tarjan(int x,int edge)
{
dfn[x]=low[x]=++cnt;
sta.push(x);
for(int i=head[x];i;i=nxt[i])
{
int g=go[i];
if(!dfn[g])
{
tarjan(g,i);
low[x]=min(low[x],low[g]);
if(low[g]>dfn[x])//非常正常的求桥
{
ans++;
int p;
do{
p=sta.top();
sta.pop();
dcc[ans].push_back(p);
}while(p!=g);//记录答案
//这里不再需要加入当前节点了
}
}
else if(i!=(edge^1)) //不走回头路
low[x]=min(low[x],dfn[g]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
if(u==v) continue;//重边
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
add(i+n,i);//虚拟点tarjan
tarjan(i+n,0);
}
}
printf("%d\n",ans);
for(int i=1;i<=ans;i++)
{
printf("%d ",dcc[i].size());
for(int j=0;j<dcc[i].size();j++)
printf("%d ",dcc[i][j]);
puts("");
}
}
相对于割裂出来各个子图,在进行搜索的做法,省略了一次搜索和两个标记数组。但是导致了边数和点数的增多以及在原来的 Tarjan 需进行栈操作,也许互有优劣?