问一个无向图上面,连最少的边,使之成为一个双连通图.求出连边的具体方法。
一开始首先要找出图里的双连通分量, 把他们缩成一个点后再处理.
【双连通分量】
求双连通分量的方法和Tarjan差不多, 改一下就好了.
定义: "割点" & "桥" (上网有很多啊, 传送门:http://www.byvoid.com/blog/biconnect/zh-hans/)
题目中要求的是“边双联通”,所以求的是桥.
定义: DFN & LOW (DFN是在DFS树里序号, LOW是不经过父亲边可到达的最浅深度)
所以, LOW[X]= MIN (MIN (DFN[Y]) {X---Y为横叉边,到达的是自己的祖先} , MIN(LOW[Y]) {X---Y是到达未访问过的子节点} )
桥,即为 对于一条边(U----V) , DFN[U] < LOW[V] (儿子节点不能到达比自己浅的节点)
遇到桥时, 就把儿子所在的子树全部取出,标记.
Pro:
void tarjan(int x,int last){
dfn[x]=low[x]=++indexs;
s[++top]=x; // 进栈
for(int p=end[x]; p>0; p=next[p]){
if((p^1) ==last) continue; // 若不等于上一次的边
else {
if(dfn[to[p]]==0) { // 若没有访问
tarjan(to[p],p);
if (low[to[p]]< low[x]) low[x]=low[to[p]]; // 更改 low
else if ( low[to[p]]> dfn[x] ){ // p 边为 "桥"
while(s[top]!=to[p]) col[s[top--]]=num; // 依次退栈
col[to[p]]=num;
--top;
num++;
}
}
else oMin(low[x], dfn[to[p]]);
}
}
}
还好啦.
【求最少边数】
传送门见。
【求边】
每一个点,对于他的每一个子节点, 把他与自己合并。
若某一时刻点的节点数大于2(等于2 就没有边往上连),就把它们某两个节点(不在一个子树)相连, 再合并成一个点。
对于根节点, 若只有一个子树,把节点里的每一个点与根连;
否则 若有两个点, 他们相连; 一个点, 与根相连。
Pro:
state Dfs(int x,int last){
state a;
a.t=0;
bool leaf=1;
for(int p=end[x]; p>0; p=next[p]){
if(to[p] != last ){
leaf=0;
state c=Dfs(to[p], x);
if(a.t+ c.t>2) { // 相连 ---
line(c.a, a.a);
if(a.t+c.t==4){a.t=2; a.a= a.b; a.b=c.b;} // -4个点-
else if(a.t==2){a.t=1; a.a=a.b;} // -3个点-
else {a.t=1; a.a=c.b;}
}
else {
if(a.t==1) {a.t++; a.b=c.a;} // 直接合并
else {a=c;}
}
}
}
if(leaf){ ++ans; state ret; ret.t=1; ret.a=x; return ret; }
return a;
}
void work(){
ans=Edge=0;
int root= low[1];
state q=Dfs(low[1],0);
int time=0;
for(int p=end[root]; p>0; p=next[p]) ++time; // 特判
if(time==0) {printf("0\n"); return;}
if(time==1) {
line(q.a, root);
if(q.t==2) line(q.b, root);
}
else {
if(q.t==1) line(q.a, root);
else line(q.a, q.b);
}
printf("%d\n",(ans+1)/2);
for(int i=1; i<=(ans+1)/2; i++) printf("%d %d\n",l[i][0], l[i][1]);
}