2730: [HNOI2012]矿场搭建
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 2340 Solved: 1086
[Submit][Status][Discuss]
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
Sample Output
Case 1: 2 4
Case 2: 4 1
HINT
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
Source
day1
题解:
对于那些不是割点的点,如果它塌了,对答案肯定没有影响,所以考虑删掉每个割点。
原图被分为了若干个联通块。是不是对于每个联通块都需要再建一个呢?不然。
(1)如果这个联通块只与一个割点相连,那么肯定要在块内再建一个,方案数为siz,siz代表联通块大小。
(2)如果这个联通块与两个及以上的割点相连,那么即使有一个割点塌掉,仍然可以从另一个割点逃生,所以对于与两个以上割点相连的联通块,可以不用再建。
(3)如果这个联通块本来就是与其他联通块分离,没有割点与它相连,那么块内至少选2个,方案数为C(2,siz)。[其实数据好像没有这种情况。
如果原图本身就是一个点双联通分量,没有割点,就与上述情况的3类似,只需要建2个,方案数为C(2,n)。
当且仅当原图中只有1个点时,方案数才为1。【但这样一塌就不满足都能跑出去了…所以其实不考虑也行。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N = 500 + 10;
int n,m;
struct node{
int pre,v;
}e[N<<1];
int head[N],num=0;
void addedge(int from,int to){
e[++num].pre=head[from],head[from]=num;
e[num].v=to,head[from]=num;
}
int indx=0,low[N],dfn[N];
int tot=0,iscut[N];
void tarjan(int u,int f){
low[u]=dfn[u]=++indx;
int son=0;
for(int i=head[u];i;i=e[i].pre){
int v=e[i].v;
if(v==f) continue;
if(!dfn[v]){
++son;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&!iscut[u]) ++tot,iscut[u]=true;
}
else low[u]=min(low[u],dfn[v]);
}
if(son==1&&f==0) {if(iscut[u]) tot--;iscut[u]=false;}
}
int vis[N],cnt=0,siz;
int cutnum=0;
void dfs(int u){
vis[u]=cnt;++siz;
for(int i=head[u];i;i=e[i].pre){
int v=e[i].v;
if(iscut[v]&&vis[v]!=cnt) {vis[v]=cnt;++cutnum;continue;}
if(!vis[v]) dfs(v);
}
}
int ti=0;
#define ms(x,y) memset(x,y,sizeof(x))
void update(){
num=0;ms(head,0);ms(low,0);ms(dfn,0);indx=0;
cnt=0;tot=0;ms(iscut,0);ms(vis,0);n=0;
}
int main(){
while(1){
m=read();
if(m==0) break;
update();
for(int i=1;i<=m;++i){
int s=read(),t=read();
addedge(s,t);addedge(t,s);
n=max(s,n);n=max(t,n);
}
for(int i=1;i<=n;++i) {
if(!dfn[i]) tarjan(i,0);
}
if(tot==0) {
ll ans1=2,ans2=n*(n-1)/2;
printf("Case %d: %lld ",++ti,ans1);
printf("%lld\n",ans2);
continue;
}
ll ans1=0,ans2=1;
for(int i=1;i<=n;++i){
if((!vis[i])&&(!iscut[i])) {
cutnum=0,++cnt,siz=0;
dfs(i);
if(!cutnum) ans1+=2,ans2*=siz*(siz-1)/2;
if(cutnum==1) ++ans1,ans2=ans2*siz;
}
}
printf("Case %d: %lld ",++ti,ans1);
printf("%lld\n",ans2);
}
return 0;
}