题目大意:无向简单图,添加尽量少的边使得图为边-双连通。输出最少需要添加的边条数。
这儿有个结论:对于一棵树,至少添加(其叶子节点+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; }