BZOJ2730: [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)。
题解Here!
思路:找出割点和所有的双连通分量,然后统计每个双连通分量里的个点的个数分情况讨论。
-
若该连通分量里有不少于两个割点,则它是安全的,因为无论哪个割点炸了,里面的点可以通过其他的没炸的割点跑到其他的双连通分量里去。
-
若该连通分量里只有一个割点,那么如果这个割点炸了,则里面的点就不可能跑到其他的双连通分量里去了,所以要在这个割点里建一个出口。
-
若该连通分量里一个割点也没有,说明它与外界完全不连通,这时如果只建一个出口的话,那么如果这个出口炸了就 GG ,所以还需要另一个出口“以防万一”(即建两个出口)
对于方案数的话,我们发现如果要建出口的话,该双连通分量里的任何一个非割点的节点都是可以的,因此用一下组合数学就可以搞定了。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define MAXN 510 using namespace std; int n,m,c,d,num,times,size; long long ans,sum; int head[MAXN],deep[MAXN],low[MAXN],fa[MAXN],vis[MAXN]; bool flag[MAXN]; struct Graph{ int next,to; }a[MAXN<<1]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline void add(int x,int y){ a[c].to=y;a[c].next=head[x];head[x]=c++; a[c].to=x;a[c].next=head[y];head[y]=c++; } void dfs(int x){ int s=0; deep[x]=low[x]=d++; for(int i=head[x];i;i=a[i].next){ int v=a[i].to; if(!deep[v]){ fa[v]=fa[x]; dfs(v); low[x]=min(low[x],low[v]); if(low[v]>=deep[x]&&x!=fa[x])flag[x]=true; if(x==fa[x])s++; } low[x]=min(low[x],deep[v]); } if(x==fa[x]&&s>=2)flag[fa[x]]=true; } void answer(int x){ size++; vis[x]=times; for(int i=head[x];i;i=a[i].next){ int v=a[i].to; if(vis[v]!=times&&flag[v]){ num++; vis[v]=times; } if(!vis[v]&&!flag[v])answer(v); } } void work(){ for(int i=1;i<=n;i++) if(!vis[i]&&!flag[i]){ size=num=0; times++; answer(i); if(num==0){ans+=2;sum*=size*(size-1)/2;} if(num==1){ans++;sum*=size;} } printf("%lld %lld\n",ans,sum); } void init(){ c=d=sum=1; n=times=ans=0; memset(head,0,sizeof(head)); memset(deep,0,sizeof(deep)); memset(low,0,sizeof(low)); memset(flag,false,sizeof(flag)); memset(vis,0,sizeof(vis)); int x,y; for(int i=1;i<=m;i++){ x=read();y=read(); n=max(n,max(x,y)); add(x,y); } for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<=n;i++)if(!deep[i])dfs(i); } int main(){ for(int cases=1;;cases++){ m=read(); if(m==0)break; printf("Case %d: ",cases); init(); work(); } return 0; }