1.计算双连通子图
所谓双连通子图指的是没有割点的连通子图,亦称块。这个子图中的任何一对节点至少存在两条不相交的路径,或者说要使这个子图不连通,至少要删除块内两个节点。
块是图中边的划分,即图中的每条边都包含在某个块中。
不同的两块不含公共边,不同的两个块最多有一个公共节点,此节点就是原图的割点。
求点双连通分支
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
模板:
program find_BCC;
const
maxn=100;
maxm=10000;
Type //边类型
edge=record
u,v,next:integer;
end;
var
g:array[1..maxm] of edge; //边表,静态连实现
h:array[1..maxn] of integer;//节点头指针
stack:array[1..maxm] of edge;//栈
bcc:array[1..maxn,0..maxn] of integer;
//双连通分量集合
pre,low,bccNo:array[1..maxn] of integer;
isCut:array[1..maxn] of boolean;
//判断割点
top,n,m,u,v,i,j,time,bccCnt,st:integer;
//bccCnt表示双连通分量序号,st为栈顶指针
procedure findBCC(u,fa:integer);
var
v,child,tmp:integer;
begin
inc(time); pre[u]:=time; low[u]:=time;
tmp:=h[u]; child:=0;
while tmp<>-1 do
begin
v:=g[tmp].v;
if pre[v]=-1 then
begin
inc(child);
push(g[tmp]);
findBCC(v,u);
low[u]:=min(low[u],low[v]);
if low[v]>=pre[u] then
begin
iscut[u]:=true;
inc(bccCnt); pop(g[tmp]);
end;
end
else if (v<>fa) and (pre[v]<pre[u]) then
begin
push(g[tmp]);
low[u]:=min(low[u],pre[v]);
end;
tmp:=g[tmp].next;
end;
if (child=1) and (fa=-1) then iscut[u]:=false;
end;
procedure pop(e:edge);
var tmp:edge;
begin
repeat
tmp:=stack[st];
dec(st);
if bccNo[tmp.u]<>bccCnt then
begin
inc(bcc[bccCnt,0]);
bcc[bccCnt,bcc[bccCnt,0]]:=tmp.u;
bccNo[tmp.u]:=bccCnt;
end;
if bccNo[tmp.v]<>bccCnt then
begin
inc(bcc[bccCnt,0]);
bcc[bccCnt,bcc[bccCnt,0]]:=tmp.v;
bccNo[tmp.v]:=bccCnt;
end;
until (tmp.u=e.u) and (tmp.v=e.v);
end;
procedure push(e:edge);
begin
inc(st);
stack[st]:=e;
end;
procedure addedge(u,v:integer);
begin
inc(top);
g[top].u:=u;
g[top].v:=v;
g[top].next:=h[u];
h[u]:=top;
inc(top);
g[top].u:=v;
g[top].v:=u;
g[top].next:=h[v];
h[v]:=top;
end;
begin
fillchar(h,sizeof(h),$ff);
top:=0;
readln(n,m);
for i:=1 to m do
begin
readln(u,v);
addedge(u,v);
end;
time:=0;
bccCnt:=0;
st:=0;
fillchar(isCut,sizeof(iscut),false);
fillchar(bccNo,sizeof(bccNo),0);
fillchar(pre,sizeof(pre),$ff);
fillchar(bcc,sizeof(bcc),0);
for i:=1 to n do
if pre[i]=-1 then
findBcc(i,-1);
writeln('BCC:',bccCnt);
for i:=1 to bccCnt do
begin
for j:=1 to bcc[i,0] do
begin
write(bcc[i,j],' ');
end;
writeln;
end;
write('The cut point is:');
for i:=1 to n do if iscut[i] then write(i, ' ');
writeln;
end.
2.求边双连通分支
对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
构造双连通图
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
下面有几道题:
POJ 3177 Redundant Paths
POJ 3352 Road Construction
POJ 3694 Network