矿场搭建[HNOI2012]
时间限制:1 s 内存限制:128 MB
【题目描述】
【输入格式】
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖煤点 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
【输出格式】
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于2^64。输出格式参照以下输入输出样例。
【样例输入】
9
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
【样例输出】
Case 1: 2 4
Case 2: 4 1
【提示】
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
【题解】
双联通分量这种东西,总感觉自己就没有学过的样子……或许学过,大概打了几道板子题,从此一定再也没看过吧。为了做这道题去翻原来的课件,又上网找资料,勉强把板子打出来。对于求割点的事记得也不是很清楚,但是这道题大概是求割点的。割点的定义就是去掉它之后联通块的数量增多,其实也就是个“交通枢纽”一类的概念。这种点还是比较特殊的,在很多实际应用题里面也能找到很好的背景。
对于这道题来说,如果只是一个单独的联通块,在里面随便两个点建通道就可以了。如果有两个及以上的割点,那根本就不用建,因为塌了一个还有别的。真正关键的是只有一个割点,这样在每个双联通分量里必须有一个通道,而选址则是在除了割点以外的其他地方。
实现上和tarjan缩点不太一样的地方就是在遍历完所有儿子之前就可以开始退栈,只要low[nt]>=dfn[x]。板子说到底还是要打的,多打几遍记得也牢理解也深了。书读千遍其义自见,或许不是没有道理的。
#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; const int sj=505; int n,a1,a2,e,h[sj],low[sj],dfn[sj],top,s[sj],jg,mn; vector<int> bl[sj]; bool r[sj],c[sj]; long long ans; struct B { int ne,v; }b[sj<<1]; void add(int x,int y) { b[e].v=y; b[e].ne=h[x]; h[x]=e++; } void bj(int &x,int y) { x=x<y?x:y; } void tarjan(int x,int fx) { int son=0,nt; low[x]=dfn[x]=++a1; s[top++]=x; r[x]=1; for(int i=h[x];i!=-1;i=b[i].ne) { nt=b[i].v; if(nt==fx) continue; if(!dfn[nt]) { tarjan(nt,x); bj(low[x],low[nt]); if(x==fx) son++; if(low[nt]>=dfn[x]) { a2++; int vn; bl[a2].clear(); do { vn=s[--top]; bl[a2].push_back(vn); r[vn]=0; }while(vn!=nt); bl[a2].push_back(x); if(x!=fx) c[x]=1; } } else if(r[nt]) bj(low[x],dfn[nt]); } if(x==fx&&son>=2) c[x]=1; } int main() { for(int q=1;;q++) { scanf("%d",&n); if(n==0) break; top=e=jg=ans=mn=0; memset(h,-1,sizeof(h)); memset(dfn,0,sizeof(dfn)); memset(r,0,sizeof(r)); memset(c,0,sizeof(c)); for(int i=1;i<=n;i++) { scanf("%d%d",&a1,&a2); add(a1,a2); add(a2,a1); if(a1>mn) mn=a1; if(a2>mn) mn=a2; } a1=a2=0; for(int i=1;i<=mn;i++) if(!dfn[i]) tarjan(i,i); if(a2==1) { ans=(mn-1)*mn/2; printf("Case %d: 2 %lld\n",q,ans); continue; } ans=1; for(int i=1;i<=a2;i++) { a1=0; for(int j=0;j<bl[i].size();j++) if(c[bl[i][j]]) a1++; if(a1==1) jg++,ans*=(bl[i].size()-1); } printf("Case %d: %d %lld\n",q,jg,ans); } return 0; }
想起来唐山很多年前据说在院子里挖个深坑拿井绳吊下去就可以采煤的传说23333,也不知道真的假的。