[BZOJ2730]:[HNOI2012]矿场搭建(塔尖)
题目传送门
题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入格式
输入文件有若干组数据,每组数据的第一行是一个正整数N(N≤500),表示工地的隧道数,接下来的N行每行是用空格隔开的两个整数S和T,表示挖煤点S与挖煤点T由隧道直接连接。输入数据以0结尾。
输出格式
输入文件中有多少组数据,输出文件中就有多少行。每行对应一组输入数据的结果。
其中第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)(2,4),(3,4),(4,5),(4,6)(2,4),(3,4),(4,5),(4,6);
Case 2的一组解为(4,5,6,7)(4,5,6,7)(4,5,6,7)。
N≤500,输入数据保证答案小于${2}^{64}$
题解
首先,肯定是要考虑塔尖算法的,判除割点后,在一个强联通分量内分一下三种情况:
1.如果没有割点需要建两个出口,因为有可能其中一个会坍塌。
2.如果有一个割点,则在非割点的其中一个割点建一个出口,割点坍塌可以从这个出口逃出,如果这个出口坍塌则从割点出去。
3.如果有两个即以上割点,则不用建出口,直接从其他个嗲逃出即可。
统计完这三种情况之后,用乘法原理计算答案即可。
代码时刻
#include<bits/stdc++.h> using namespace std; struct rec { int nxt; int to; }e[100001]; int n; int head[100001],cnt; int dfn[100001],low[100001],sta[100001],top,tot,root; bool cut[100001]; int ans; long long sum; vector<int> dcc[100001]; int biu; void pre_work() { cnt=1; top=0; tot=0; ans=0; sum=1; n=0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(head,0,sizeof(head)); memset(sta,0,sizeof(sta)); memset(cut,0,sizeof(cut)); } void add(int x,int y) { e[++cnt].nxt=head[x]; e[cnt].to=y; head[x]=cnt; } void tarjan(int x)//塔尖判割点 { dfn[x]=low[x]=++tot; sta[++top]=x; int flag=0; for(int i=head[x];i;i=e[i].nxt) { if(!dfn[e[i].to]) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]<=low[e[i].to]) { flag++; if(x!=root||flag>1)cut[x]=1; cnt++; dcc[cnt].clear(); int y; do { y=sta[top--]; dcc[cnt].push_back(y); }while(e[i].to!=y); dcc[cnt].push_back(x); } } else low[x]=min(low[x],dfn[e[i].to]); } } int main() { while(1) { int m; scanf("%d",&m); if(!m)break; pre_work(); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); n=max(n,max(x,y)); } cnt=0; for(int i=1;i<=n;i++) if(!dfn[i])root=i,tarjan(i); if(cnt==1) { printf("Case %d: 2 %lld\n",++biu,1LL*n*(n-1)/2);//特判 continue; } for(int i=1;i<=cnt;i++) { int flag=0; for(int j=0;j<dcc[i].size();j++) if(cut[dcc[i][j]])flag++; if(flag==1) { ans++; sum*=dcc[i].size()-1;//乘法原理统计答案 } } printf("Case %d: %d %lld\n",++biu,ans,sum); } return 0; }
rp++。