[HNOI2012]矿场搭建
Description
Input
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
Output
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。
Sample Input
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
Sample Output
Case 2: 4 1
HINT
题解:
1.这道题牵涉到我们省选以前很少用过的东西——双连通分量。
这题就和点双连通有关,回顾一下点双连通。
定义:在一个点双连通图中,任意连个不同的点都至少有两条不经过同一个点的路径将它们相连。
从定义中我们可以看出,在一个点双连通图中,去掉任意一个点,其它的点都是联通的。也就是说,对于一个点双连通图,我们要建两个出口,那么即使一个出口坍塌,我们还可以从另外一个出口出去。
2.这只是一个很笼统的想法,因为题目所给的图不一定是点双连通图。但是可以肯定的是,任何一个图都是由很多个点双连通组成的,而且点双连通两两之间都是用割点链接起来的(如果是桥,那么就是两个割点),也就是说我们可以把所给的图拆分成很多个点双连通。
3.那么拆分出来的点双连通都会和0个,1个,2个,或者更多个点双联通分量相连(也就是和割点相连)。下面我们来分类讨论:
Zero 当和0个割点相连时,如上文所说,要建两个出口。
One 当和1个割点相连时,要建1个出口,若果割点炸了,那么还可以从这个出口出去。
Two or more 当和2个或两个以上个割点相连时,不需要建出口,因为一个割点炸了,还可以从另外一个割点到达别的点双联通分量。
4.对于题目第二问求方案数,直接用乘法原理就可以了。这一问很简单,自己YY一下就好。
1 //Never forget why you start 2 #include<iostream> 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<cmath> 7 #include<algorithm> 8 using namespace std; 9 typedef long long lol; 10 int n,m,t,ans; 11 lol ans2; 12 struct node { 13 int next,to; 14 } edge[1005]; 15 int head[505],size=0; 16 void putin(int from,int to) { 17 size++; 18 edge[size].next=head[from]; 19 edge[size].to=to; 20 head[from]=size; 21 } 22 void clean(); 23 int dfn[505],low[505],iscut[505],dfscnt,root,child; 24 void tarjan(int r,int fa) { 25 int i; 26 dfn[r]=low[r]=++dfscnt; 27 for(i=head[r]; i!=-1; i=edge[i].next) { 28 int y=edge[i].to; 29 if(!dfn[y]) { 30 tarjan(y,r); 31 low[r]=min(low[r],low[y]); 32 if(low[y]>=dfn[r]) { 33 if(r!=root)iscut[r]=1; 34 else child++; 35 } 36 } else if(y!=fa)low[r]=min(low[r],dfn[y]); 37 } 38 }//tarjan求割点 39 int cnt,vis[505],num,sum; 40 void dfs(int r) { 41 int i; 42 vis[r]=cnt; 43 num++; 44 for(i=head[r]; i!=-1; i=edge[i].next) { 45 int y=edge[i].to; 46 if(vis[y]!=cnt&&iscut[y]) { 47 sum++; 48 vis[y]=cnt; 49 } else if(vis[y]!=cnt)dfs(y); 50 } 51 }//遍历每个点双连通分量,sum表示和这个点双联通分量相连的割点的个数 52 int main() { 53 int i,j; 54 while(scanf("%d",&m)!=EOF) {//多组数据 55 if(m==0)break; 56 clean();//各种初始化 57 t++; 58 for(i=1; i<=m; i++) { 59 int u,v; 60 scanf("%d%d",&u,&v); 61 putin(u,v); 62 putin(v,u); 63 n=max(n,v); 64 n=max(n,u); 65 } 66 for(i=1; i<=n; i++) { 67 if(!dfn[i]) { 68 root=i; 69 child=0; 70 tarjan(i,i); 71 if(child>=2)iscut[root]=1; 72 } 73 } 74 for(i=1; i<=n; i++) { 75 if(!vis[i]&&!iscut[i]) { 76 cnt++; 77 num=0; 78 sum=0; 79 dfs(i);//遍历每一个点双连通分量 80 if(sum==0) { 81 ans+=2; 82 ans2*=num*(num-1)/2; 83 } 84 if(sum==1) { 85 ans+=1; 86 ans2*=num;//统计答案 87 } 88 } 89 } 90 printf("Case %d: %d %lld\n",t,ans,ans2); 91 } 92 return 0; 93 } 94 void clean() { 95 memset(head,-1,sizeof(head)); 96 memset(dfn,0,sizeof(dfn)); 97 memset(low,0,sizeof(low)); 98 memset(iscut,0,sizeof(iscut)); 99 memset(vis,0,sizeof(vis)); 100 size=0; 101 dfscnt=0; 102 cnt=1; 103 ans=0; 104 n=0; 105 ans2=1; 106 }//各种初始化