定义
一张连通图中,若任意两个点之间都有至少两条没有重复点的路径,则这是一个点双。
求法
和tarjan系列的算法相似,在建dfs树的同时,可以发现每个割点都会将这个图分为几个点双,且每个割点都属于多个点双,因此,我们可以在dfs的同时,将所经过的边全部存起来,每当扫到一个割点就统计这个割点所分出的点双。
代码
void dfs(int now)
{
// if(xh>D) throw(0);xh++;
int p,q,ch=0;
dfn[now]=low[now]=++tt;
for(p=first[now]; p!=-1; p=bn[p].next)
{
q=p/2;
if(use[q] || dfn[bn[p].to]>dfn[now]) continue;
use[q]=1;
sta[++st]=q;
if(dfn[bn[p].to])
{
low[now]=min(low[now],dfn[bn[p].to]);
}
else
{
ch++;
dfs(bn[p].to);
low[now]=min(low[now],low[bn[p].to]);
if(low[bn[p].to]>=dfn[now])
{
gd[now]=1;
ss[q]=++cnt;
for(;sta[st]!=q;st--)
{
ss[sta[st]]=cnt;
//此时栈中pop掉的边连的两端即为该点双所包含的点。
}
st--;
}
}
}
if(now==gen&&ch<2) gd[now]=0;
}
例题(HDU - 3686 Traffic Real Time Query System)
题意
给出一幅图,询问从其中一边到另外一边必须要经过的点有几个。
做法
不难发现,就是询问要经过几个割点。
因此可以将所有点双缩为一个点,通过割点将它们连接,形成一棵“点双—割点—点双……”的树,然后求其中两条边所在点双之间的路径上有几个割点即可。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10010
#define M 100100
#define M2 200100
#define D 10000000
using namespace std;
int n,m,bb,bb2,Q,tt,gj,gen,dfn[N],low[N],first[N],f2[N],cnt,fa[N],deep[N],top[N],so[N],size[N],dz[N],fz[N],st,ss[M],tmp[N],sta[M],xh;
bool gd[N],use[M],vis[N];
struct Bn
{
int to,next;
} bn[M2],b2[M2];
inline void add(int u,int v)
{
bb++;
bn[bb].to=v;
bn[bb].next=first[u];
first[u]=bb;
}
inline void ad2(int u,int v)
{
bb2++;
b2[bb2].to=v;
b2[bb2].next=f2[u];
f2[u]=bb2;
}
void dfs(int now)
{
int p,q,ch=0;
dfn[now]=low[now]=++tt;
for(p=first[now]; p!=-1; p=bn[p].next)
{
q=p/2;
if(use[q] || dfn[bn[p].to]>dfn[now]) continue;
use[q]=1;
sta[++st]=q;
if(dfn[bn[p].to])
{
low[now]=min(low[now],dfn[bn[p].to]);
}
else
{
ch++;
dfs(bn[p].to);
low[now]=min(low[now],low[bn[p].to]);
if(low[bn[p].to]>=dfn[now])
{
gd[now]=1;
ss[q]=++cnt;
for(;sta[st]!=q;st--)
{
ss[sta[st]]=cnt;
}
st--;
}
}
}
if(now==gen&&ch<2) gd[now]=0;
}
void Dfs(int now,int last)
{
vis[now]=1;
int i,j,t,mx=0,p,q;
size[now]=1;
for(p=f2[now]; p!=-1; p=b2[p].next)
{
t=b2[p].to;
if(t==last || vis[t]) continue;
deep[t]=deep[now]+(t>gj);
dz[t]=dz[now]+1;
fz[t]=now;
Dfs(t,now);
size[now]+=size[t];
if(size[t]>mx)
{
mx=size[t];
so[now]=t;
}
}
}
void D2(int now,int last)
{
int i,j,t,p,q;
if(!so[now]) return;
else
{
top[so[now]]=top[now];
D2(so[now],now);
}
for(p=f2[now]; p!=-1; p=b2[p].next)
{
t=b2[p].to;
if(t==last || t==so[now]) continue;
top[t]=t;
D2(t,now);
}
}
inline int lca(int u,int v)
{
for(; top[u]!=top[v];)
{
if(dz[top[u]]<dz[top[v]]) swap(u,v);
u=fz[top[u]];
}
return dz[u]<dz[v]?u:v;
}
int main()
{
int i,j,p,q,l;
while(~scanf("%d%d",&n,&m))
{
if(!n&&!m) return 0;
tt=cnt=st=bb2=0,bb=1;
memset(first,-1,sizeof(first));
memset(f2,-1,sizeof(f2));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(so,0,sizeof(so));
memset(use,0,sizeof(use));
memset(vis,0,sizeof(vis));
memset(gd,0,sizeof(gd));
for(i=1; i<=m; i++)
{
scanf("%d%d",&p,&q);
add(p,q),add(q,p);
}
for(i=1; i<=n; i++) if(!dfn[i]) gen=i,dfs(i);
gj=cnt;
for(i=1; i<=n; i++)
{
if(gd[i]) tmp[i]=++cnt;
}
for(i=1;i<=m;i++)
{
p=(i << 1),q=(i << 1)+1;
if(gd[bn[p].to]) ad2(tmp[bn[p].to],ss[i]),ad2(ss[i],tmp[bn[p].to]);
if(gd[bn[q].to]) ad2(tmp[bn[q].to],ss[i]),ad2(ss[i],tmp[bn[q].to]);
}
for(i=1; i<=cnt; i++)
{
if(vis[i]) continue;
deep[i]=(i>gj);
dz[i]=0;
top[i]=i;
Dfs(i,-1);
D2(i,-1);
}
scanf("%d",&Q);
for(i=1; i<=Q; i++)
{
scanf("%d%d",&p,&q);
p=ss[p],q=ss[q];
l=lca(p,q);
printf("%d\n",deep[p]+deep[q]-2*deep[l]+(l>gj));
}
}
}