洛谷 P3225 [HNOI2012] 矿场搭建
在大家都在努力的考试的最后半个小时里,不会做考试题的我来水一个题解。
题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入输出格式
输入格式:
输入文件有若干组数据,每组数据的第一行是一个正整数 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),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
思路:
我们需要先找出割点,然后是所有的双连通分量,最后统计出每个双连通分量里的个点的个数分类讨论。
-
若该连通分量里有不少于两个割点,则它是安全的,因为无论哪个割点炸了,里面的点可以通过其他的没炸的割点跑到其他的双连通分量里去。
-
若该连通分量里只有一个割点,那么如果这个割点炸了,则里面的点就不可能跑到其他的双连通分量里去了,所以要在这个割点里建一个出口。
-
若该连通分量里一个割点也没有,说明它与外界完全不连通,这时如果只建一个出口的话,那么如果这个出口炸了就GG,所以还需要另一个出口“以防万一”(即建两个出口)的!!
对于方案数的话,我们发现如果要建出口的话,该双连通分量里的任何一个非割点的节点都是可以的,那么就可以搞定了啊。
这其实是个数学题啊!!
放上代码。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; const int MA=501; int n,sum,root,num; int dfn[MA],low[MA],sk[MA]; bool cut[MA]; int tp,ti,cnt; vector<int> q[MA],g[MA]; inline int read() { int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x; } void Tar(int x) { dfn[x]=low[x]=++ti; sk[++tp]=x; int l=q[x].size(),sl=0; for(int i=0;i<l;i++) { int j=q[x][i]; if(!dfn[j]) { sl++; Tar(j); low[x]=min(low[x],low[j]); if(x==root&&sl>1) cut[x]=1; if(x!=root&&dfn[x]<=low[j]) cut[x]=1; if(dfn[x]<=low[j]) { g[++cnt].clear(); do { g[cnt].push_back(sk[tp--]); }while(sk[tp+1]!=j); g[cnt].push_back(x); } } else low[x]=min(low[x],dfn[j]); } } void mem() { memset(cut,0,sizeof(cut)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); tp=cnt=ti=sum=0; for(int i=1;i<=MA;i++) q[i].clear(); return; } int main() { //freopen("1 (2).txt","w",stdout); while(scanf("%d",&n)!=EOF&&n) { mem(); for(int i=1;i<=n;i++) { int a=read(); int b=read(); q[a].push_back(b); q[b].push_back(a); sum=max(max(a,b),sum); } for(int i=1;i<=n;i++) if(!dfn[i]) { root=i; Tar(i); } long long ans1=0,ans2=1; for(int i=1;i<=cnt;i++) { int tg=0; int l=g[i].size(); for(int j=0;j<l;j++) if(cut[g[i][j]]) tg++; if(!tg) { ans1+=2; ans2=ans2*(long long)(l*(l-1)/2); } else if(tg==1) { ans1++; ans2=ans2*(long long)(l-1); } } printf("Case %d: ",++num); cout<<ans1<<" "<<ans2<<endl; } return 0; }
完成!!
谢谢。