[HNOI2012]矿场搭建 点双连通分量

NKOJ3985 [HNOI2012]矿场搭建

问题描述

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

输入格式

有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,
接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 煤点S 与挖煤点 T 由隧道直接连接。
输入数据以 0 结尾。

输出格式

输入有多少组数据,输出就有多少行。每行对应一组输入数据的 结果。
其中第 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
Case 2: 4 1

提示

Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。


首先容易想到割点,因为删去割点后,分割出的两个或以上的点双联通分量中,每一个都至少要有一个救援出口。而且在最优方案里,割点上是不能够设置救援出口的。所以算法是显然的:用Tarjan求点双连通分量。

首先求出所有割点。那么首先有一个坑点:如果根本就没有割点,那么这个点双联通分量中,至少要有两个救援出口,而且每个都可以被选择,这时的选择方案数是 C2size 种,即 12size(size1) 种。

下面讨论有割点的情况。

如果某点双连通分量连接的割点只有一个,那么该双连通分量里必须设置一个救援出口,方案数是 size

如果某点双连通分量连接的割点有两个或以上,那么删去其中一个割点后,必然还剩下一些与之相连割点,这个时候只要剩下的割点连接的点双连通分量中有救援出口,它就可以利用它们的救援出口,因此这个点双连通分量中是不需要设置出口的。

在DFS求出点双连通分量时顺便求出连接的割点数即可。最后的方案数采用乘法原理处理。

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define ll long long
#define Min(x,y) ((x<y)?(x):(y))
#define Max(x,y) ((x>y)?(x):(y))
#define MAXM 10005
#define MAXN 10005
using namespace std;

int N,T,P,mark[MAXN],Ans_cnt;
ll Ans_tot;

int tot,en[MAXM],nex[MAXM],las[MAXN];
void ADD(int x,int y)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
}

int pbc,VT,dfn[MAXN],low[MAXN],be[MAXN];
bool Cut[MAXN];
void Tarjan(int x,int f)
{
    dfn[x]=low[x]=++VT;

    int i,y,son=0;

    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(!dfn[y])
        {
            Tarjan(y,x);
            son++;
            low[x]=Min(low[x],low[y]);
            if(low[y]>=dfn[x]&&f)Cut[x]=true;
        }
        else if(y!=f)low[x]=Min(low[x],dfn[y]);
    }
    if(f==0&&son>=2)Cut[x]=true;
}

bool vis[MAXN];
int con[MAXN],Size[MAXN],CTime[MAXN];
//con记录与该点双连通分量连接的节点数,CTime名字起得略差,用来标记割点是否已经被当前的点双连通分量讨论过
void DFS(int x)
{
    int i,y;
    vis[x]=true;Size[pbc]++;
    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(vis[y]||Cut[y])
        {
            if(Cut[y]&&CTime[y]!=pbc)con[pbc]++,CTime[y]=pbc;
            continue;
        }
        DFS(y);
    }
}

void init()
{
    T++;VT=tot=P=pbc=Ans_cnt=0;
    Ans_tot=1;

    memset(las,0,sizeof(las));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(Cut,false,sizeof(Cut));
    memset(mark,false,sizeof(mark));
    memset(vis,false,sizeof(vis));
    memset(be,0,sizeof(be));
    memset(Size,0,sizeof(Size));
    memset(con,0,sizeof(con));
    memset(CTime,0,sizeof(CTime));
}

int main()
{
    int i,x,y;

    scanf("%d",&N);

    while(N)
    {
        init();

        for(i=1;i<=N;i++)
        {
            scanf("%d%d",&x,&y);
            ADD(x,y);ADD(y,x);
            P=Max(P,x);P=Max(P,y);
            mark[x]=mark[y]=true;
        }

        for(i=1;i<=P;i++)if(!dfn[i])Tarjan(i,0);
        for(i=1;i<=P;i++)if(mark[i]&&(!vis[i])&&(!Cut[i]))pbc++,DFS(i);

        for(i=1;i<=pbc;i++)
        {
            if(con[i]==0)Ans_tot*=1ll*(Size[i]-1)*Size[i]/2,Ans_cnt+=2;
            else if(con[i]==1)Ans_tot*=Size[i],Ans_cnt++;
        }

        printf("Case %d: %d %lld\n",T,Ans_cnt,Ans_tot);

        scanf("%d",&N);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值