题目大意:无向简单图,添加尽量少的边使得图为边-双连通。输出最少需要添加的边条数。
这儿有个结论:对于一棵树,至少添加(其叶子节点+1)/2条边,便可得到一个边-双连通图。在网上找了很久,没有找到严格的证明。不过可以这样理解:对于一棵树,首先可以找到它的直径(或者公共祖先最远的两个叶子节点),对于直径两端的叶子节点连一条边,这样可以尽可能多地使得桥数目减少。然后再找剩下的叶子节点中公共祖先最远的两个,再连一条边,依次下去。得到的结果就是那样。
根据这个结论就比较容易想到解法了:先进行BCC缩点,转换为一颗树,然后统计该树中度数为1的节点个数(叶子节点),根据结论便可得到答案。
求BCC的写法与SCC的写法大致一样,只是加了个参数,防止对一条无向边访问两次。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stack>
using namespace std;
#define N 200005
struct Edge{
int to,next;
bool flag;
}edge[2000005];
int dfn[N],low[N],bccno[N],head[N],bridge_num,cnt,dfs_clock,bcc_cnt;
stack<int> S;
void add(int u,int v){
edge[cnt].flag=0;
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs(int u,int fa){
dfn[u]=low[u]=++dfs_clock;
S.push(u);
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==fa) continue; //保证由一条无向边拆成的两条有向边只访问一条
if(!dfn[v]){
dfs(v,u);
low[u]=min(low[u],low[v]);//用后代的low函数更新自身
if(low[v]>dfn[u]){
++bridge_num;
edge[i].flag=edge[i^1].flag=1;
}
}
else if(!bccno[v]){
low[u]=min(low[u],dfn[v]);//用反向边更新
}
}
if(low[u]==dfn[u]){
++bcc_cnt;
for(;;){
int x=S.top();S.pop();
bccno[x]=bcc_cnt;
if(x==u) break;
}
}
}
void find_bcc(int n){
dfs_clock=bcc_cnt=bridge_num=0;
memset(bccno,0,sizeof(bccno));
memset(dfn,0,sizeof(dfn));
for(int i=0;i<n;++i) if(!dfn[i]) dfs(i,-1);
}
int deg[N];
int main()
{
int n,m,i,j,x,y;
while(~scanf("%d%d",&n,&m))
{
memset(head,-1,sizeof(head));
memset(deg,0,sizeof(deg));
cnt=0;
for(i=0;i<m;++i){
scanf("%d%d",&x,&y);
add(--x,--y);
add(y,x);
}
find_bcc(n);
for(i=0;i<n;++i){
for(j=head[i];~j;j=edge[j].next){
if(edge[j].flag) deg[bccno[i]]++;
}
}
int ans=0;
for(i=1;i<=bcc_cnt;++i) if(deg[i]==1) ++ans;
printf("%d\n",(ans+1)/2);
}
return 0;
}