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)。
Source
HNOI蒯白书原题啊...
本题的模型是:在无向图上选择尽量少的点涂黑,使得任意删除一个点后,每个连通分量至少有一个黑点.
看到这种题首先就进行点双连通分量的缩点(因为是删点),然后这就是一棵树,且树上的节点是以割点为交集...
对于叶子节点(即只有一个割点),则该连通分量必须建一个,不然与爸爸的割点爆掉后就gi了
(如果自己里面的爆掉,该点双内所有点都可通过与爸爸的割点往外跑,跑到另一个叶子节点就可以了)
对于非叶子节点,就不用建,爆了和爸爸的还可以往儿子那里跑,跑到叶子节点就安全了
特判如果没有割点的话(就是只有一个叶子节点,靠不了谁),就需要任选两个建
点双连通分量的实现的话,先把割点找出来,然后不走割点即可(但割点是括在点双连通分量内的)
#include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=100050; int gi(){ int x=0,flag=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') flag=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*flag; } int head[N],nxt[N],to[N],dfn[N],low[N],pd[N],tt,tong[N],cnt,tot,check[N],vis[N],vis2[N],size[N]; unsigned long long ans1,ans2; void tarjan(int x,int rt){ dfn[x]=low[x]=++tt;int flag=0; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(!dfn[y]){ tarjan(y,rt); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]) flag++; } else low[x]=min(dfn[y],low[x]); } if(x!=rt&&flag) pd[x]=1; if(x==rt&&flag>1) pd[x]=1; } void dfs(int x,int frr){ vis[x]=1,size[frr]++; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(!vis[y]&&pd[y]==0) dfs(y,frr); else if(pd[y]==1&&vis2[y]!=frr) check[frr]++,vis2[y]=frr; } } void lnk(int x,int y){ to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt; to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt; } int main(){ int T=0; while(1){ int n,x,y;T++;n=gi(); if(n==0) return 0;int num=0; ans1=0,ans2=1,tt=0,tot=0,cnt=1; for(int i=1;i<=n;i++){ int x=gi(),y=gi(); if(!tong[x]) num++,tong[x]++; if(!tong[y]) num++,tong[y]++; lnk(x,y); } for(int i=1;i<=num;i++) if(!dfn[i]) tarjan(i,i); for(int i=1;i<=num;i++) if(!vis[i]&&pd[i]==0) dfs(i,++tot); for(int i=1;i<=tot;i++){ if(check[i]==0) ans1+=2,ans2*=(size[i]-1)*(size[i])/2; else if(check[i]==1) ans1++,ans2*=size[i]; } for(int i=1;i<=num;i++) head[i]=size[i]=check[i]=pd[i]=dfn[i]=low[i]=tong[i]=vis[i]=vis2[i]=0; cout<<"Case "<<T<<":"<<' '<<ans1<<' '<<ans2<<endl; } }