BZOJ 2730: [HNOI2012]矿场搭建

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值