【刷题】BZOJ 2730 [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
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
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 1: 2 4
Case 2: 4 1
Case 2: 4 1
HINT
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
Solution
先对题目中给出的图跑一遍Tarjan,把点双和割点都求出来
然后我们发现对于每个点双,存在三种情况
1)点双中不存在割点,那么我们就要在其中最少建2个,这样就算其中有一个塌了,由于这是个点双,其它所有点还是与救生点是连通的
2)点双中只存在一个割点,那么就在除割点之外任意一个点建1个,这样如果割点塌了,其它点可以赶到救援出口;如果是救援出口塌了,那么其它点可以通过割点走到其它联通块去
3)点双中存在一个以上的割点,那么无论是那个点塌了,其它点都可以通过(剩下的)割点走到其它联通块去
对于方案数的话,用组合数可以直接算
#include<bits/stdc++.h> using namespace std; #define ll long long const int MAXN=500+10; int n,m,e,to[MAXN<<2],nex[MAXN<<2],beg[MAXN],out[MAXN],DFN[MAXN],LOW[MAXN],Visit_Num,Be[MAXN],cnt,cut[MAXN],cas,val[MAXN],C[MAXN][MAXN]; ll ans1,ans2; stack<int> s; vector<int> point[MAXN]; inline void read(int &x) { int data=0,w=1; char ch=0; while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0'&&ch<='9')data=(data<<3)+(data<<1)+(ch^'0'),ch=getchar(); x=data*w; } inline void preinit() { C[0][0]=1; for(register int i=1;i<MAXN;++i) { C[i][0]=1; for(register int j=1;j<=i;++j)C[i][j]=C[i-1][j]+C[i-1][j-1]; } } inline void init() { n=0;ans1=0;ans2=1;cnt=0;e=0; memset(Be,0,sizeof(Be)); memset(beg,0,sizeof(beg)); memset(cut,0,sizeof(cut)); memset(DFN,0,sizeof(DFN)); memset(LOW,0,sizeof(LOW)); memset(val,0,sizeof(val)); while(!s.empty())s.pop(); } inline void insert(int x,int y) { to[++e]=y; out[e]=x; nex[e]=beg[x]; beg[x]=e; } inline void Tarjan(int x,int f) { int ch=0; DFN[x]=LOW[x]=++Visit_Num; for(register int i=beg[x];i;i=nex[i]) if(to[i]==f)continue; else if(!DFN[to[i]]) { s.push(i); ch++; Tarjan(to[i],x); LOW[x]=min(LOW[x],LOW[to[i]]); if(LOW[to[i]]>=DFN[x]) { cut[x]=1; int temp; ++cnt; point[cnt].clear(); do{ temp=s.top(); s.pop(); if(Be[out[temp]]!=cnt) { Be[out[temp]]=cnt; point[cnt].push_back(out[temp]); } if(Be[to[temp]]!=cnt) { Be[to[temp]]=cnt; point[cnt].push_back(to[temp]); } }while(out[temp]!=x||to[temp]!=to[i]); } } else if(DFN[to[i]]<DFN[x]) { s.push(i); LOW[x]=min(LOW[x],DFN[to[i]]); } if(!f&&ch<2)cut[x]=0; } inline void statistic() { for(register int i=1;i<=cnt;++i) { for(register int j=0;j<point[i].size();++j) if(cut[point[i][j]])val[i]++; if(val[i]==0)ans1+=2ll,ans2*=(ll)C[point[i].size()][2]; else if(val[i]==1)ans1++,ans2*=(ll)C[point[i].size()-1][1]; } } int main() { preinit(); while(scanf("%d",&m)!=EOF&&m) { printf("Case %d:",++cas); init(); for(register int i=1;i<=m;++i) { int u,v; read(u);read(v); insert(u,v); insert(v,u); n=max(n,max(u,v)); } for(register int i=1;i<=n;++i) if(!DFN[i])Tarjan(i,0); statistic(); printf(" %lld %lld\n",ans1,ans2); } return 0; }