今天学了一下tarjan的几种常见应用,发现我还是有很多知识盲点的。
有向图的强连通分量
这是最简单的应用,关键就是:
1、若dfn[y]=0,则dfs(y),low[x]=min(low[x],low[y])
2、否则,若y在栈中,则low[x]=min(low[x],dfn[y])。这是为了防止搜到别的分量中去。
代码如下:
int dfs(int z)
{
int i;
tim++;dfn[z]=tim;low[z]=tim;
bz[z]=1;t++;d[t]=z;
for(i=first[z];i>=1&&i<=m;i=nxt[i])
if(dfn[way[i].y]==0)
{
dfs(way[i].y);
if(low[way[i].y]<low[z])low[z]=low[way[i].y];
}
else
if(bz[way[i].y]==1)
if(dfn[way[i].y]<low[z])low[z]=dfn[way[i].y];
if(dfn[z]==low[z])
{
while(t>=1)
{
bz[d[t]]=0;t--;
if(d[t+1]==z)break;
}
}
}
无向图的边双联通分量
桥
如果一个无向图中删掉了一条边之后这个图有联通变为不连通,那么这条边就是桥。
求无向图中的桥
首先说一下计算low的方式:
1、若dfn[y]=0,那么low[x]=min(low[x],low[y])
2、否则low[x]=min(low[x],dfn[y])
如果low[y]>dfn[x],那么边(x,y)就是桥。
注意一条边只能走一次。
边双联通分量
求出一个无向图中的桥,把这些桥全部删去,剩下的联通块就是边双联通分量了。
无线图的点双联通分量
割点
如果删除了一个点使无向图由联通变为不连通,那么这个点就是割点。
求无向图中的割点
首先low的计算方式与上面一样。
接着如果存在low[y]>=dfn[x],那么x就是一个割点。(注意这里要特判一下x是否为搜索树的根。如果是的话那么就要满足有两个y的low[y]>=dfn[x]在能保证x是割点。这个很好证明,如果x不是根,那么只要有一个y出现上述情况了,我们删掉x之后,y与x的父亲并不联通。但是如果x是根的话就不满足了,因为x没有父亲)
点双联通分量
对于每一个low[y]>=dfn[x],我们把栈中y及y之后的点弹出。这些弹出的点再加上x就是一个点双。
注意判断自环的情况。
一个x可能在多个点双中,而桥不可能在边双中。这使得点双比边双要难。
贴一下模板(求无向图中的割点):
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 20010
#define MAXM 200010
struct map
{
int x;
int y;
};
map way[MAXM];
int first[MAXN],nxt[MAXM],use[MAXM],dfn[MAXN],low[MAXN],bz[MAXN],root[MAXN],n,m,ans,tim;
int dfs(int z)
{
int i;
tim++;dfn[z]=tim;low[z]=tim;
for(i=first[z];i>=1&&i<=m;i=nxt[i])
if(use[i]==0)
{
use[i]=1;
if(i<=m/2)use[i+m/2]=1;
else use[i-m/2]=1;
if(dfn[way[i].y]==0)
{
dfs(way[i].y);
if(low[way[i].y]<low[z])low[z]=low[way[i].y];
}
else
if(dfn[way[i].y]<low[z])low[z]=dfn[way[i].y];
if(low[way[i].y]>=dfn[z])bz[z]++;
}
}
int main()
{
int i,j,m1=0,x,y;
scanf("%d %d",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
if(x!=y){m1++;way[m1].x=x;way[m1].y=y;}
}
m=m1;
for(i=1;i<=m;i++)way[i+m].x=way[i].y,way[i+m].y=way[i].x;
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
for(i=1;i<=n;i++)
if(dfn[i]==0){root[i]=1;dfs(i);}
for(i=1;i<=n;i++)
if(root[i]==1&&bz[i]>=2||root[i]==0&&bz[i]>=1)ans++;
printf("%d\n",ans);
for(i=1;i<=n;i++)
if(root[i]==1&&bz[i]>=2||root[i]==0&&bz[i]>=1)printf("%d ",i);
}