POJ 3352 Road Construction
题目大意:
给定图G,求最少加多少边可以成为双连通图。
解题思路:
1、求图G中的双连通分量。
2、每一个双联通分量缩成一个点,形成一棵树。
3、计算出叶子节点的个数,需要加的边的数量就是(叶子节点个数+1)/2。
解题中要注意的:
用Tarjan算法求双连通分量的时候,并不是low数组中值不同的就不再同一个双联通分量。
很多大牛的博客都只借助low数组中的值来判断是否是同一个双联通分量。的确low数组中值相同的一定是双连通,但通过Tarjan处理过的low不同也有可能是双连通。我们开始学习Tarjan算法时有一个手写栈,每一次弹栈的时候弹出来的点是一个双联通分量才对。
例如下面这组数据:
11 14 1 2 1 3 1 4 2 5 6 11 2 6 5 6 5 11 3 7 3 8 7 8 4 9 4 10 9 10
构成这样的图:
很容易看出来应该是加两条边,但很多大牛的代码处理这组数据的结果是加一条边。
但奇葩的是POJ 竟然数据水到了极点,这样也AC了。
在此注解一下:双连通分量判断是以弹栈为标准的……
下面是代码:
#include <stdio.h> #include <string.h> const int MAXN=1005; struct node { int to,next; } edge[MAXN*5]; int head[MAXN],dfn[MAXN],low[MAXN],stack1[MAXN],cou[MAXN],n,m,time,cnt,top; bool vis[MAXN]; void init() { memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(cou,0,sizeof(cou)); top=0; cnt=0; time=1; } int min(int a,int b) { if(a>b)a=b; return a; } void addedge(int u,int v) { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; cnt++; } void dfs(int u,int fa) { dfn[u]=time; low[u]=time; time++; vis[u]=true; stack1[top]=u; top++; for(int i=head[u]; i!=-1; i=edge[i].next) { int v=edge[i].to; if(v!=fa&&dfn[u]>dfn[v]) { if(!vis[v]) { dfs(v,u); low[u]=min(low[u],low[v]); } else { low[u]=min(low[u],dfn[v]); } } } if(dfn[u]==low[u]) { while(stack1[top]!=u&&top>0) { low[stack1[top-1]]=low[u]; top--; vis[stack1[top]]=false; } } } int main() { int u,v; while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=0; i<m; i++) { scanf("%d%d",&u,&v); u--; v--; addedge(u,v); //无向图加双向边 addedge(v,u); } dfs(0,-1); //因为图是联通的,只要DFS一个点,全图都可以搜到 for(int i=0; i<n; i++) { for(int j=head[i]; j!=-1; j=edge[j].next) { if(low[i]!=low[edge[j].to]) { cou[low[i]]++; cou[low[edge[j].to]]++; } } } cnt=0; for(int i=0; i<=n; i++) { if(cou[i]/2==1)cnt++; } printf("%d\n",(cnt+1)/2); } return 0; }