bzoj2730矿场搭建(Tarjan割点)

2730: [HNOI2012]矿场搭建

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1771  Solved: 835
[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)。
/*
首先看到割点就是Tarjan搞 但是怎么搞
首先假设我们把所有的点双都缩点 那么我们一定可以得到一棵树 然后我们就会发现
1.叶子节点(只含有一个割点的点双)必须建 因为叶子节点如果不建 一旦割点被爆就死翘了
2.非叶节点(含有两个或两个以上的割点的点双)不用建 因为即使一个割点被爆了也可以沿着另一个割点走到一个叶节点
3.还有一种情况就是整个联通块都是点双(即不含割点的点双) 这样我们讨论点双的大小
如果只有一个点 那么这个点必须建 数据没有卡这个的点所以我没写(其实是我忘写了 然后还过了)
如果有两个或两个以上的点 那么要建两个 一个被爆了还可以走另一个
方案数就是乘法原理的问题了 注意叶节点那里出口不能建在割点上
所以先Tarjan求割点再dfs一下每个联通块就好了。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;
int head[505],dfn[505],low[505],vis[505],stack[505];
bool cut[505],in_stack[505];
int n,m,cnt,num,tot,deg,ans1,T,cases,root,top;
ll ans2;
struct node
{
    int from;
    int to;
    int next;
}e[1010];

inline void first()
{
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    memset(vis,0,sizeof(vis));
    top=cnt=tot=n=ans1=T=0; ans2=1;
}

inline void insert(int from,int to)
{
    e[++num].from=from;
    e[num].to=to;
    e[num].next=head[from];
    head[from]=num;
}

inline int read()
{
    int x=0,f=1; char c=getchar();
    while (c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while (c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

void Tarjan(int now,int father)//求割点 
{
    dfn[now]=low[now]=++tot;
    for(int i=head[now];i;i=e[i].next)
        {
            int v=e[i].to;
            if(!dfn[v])
            {
                Tarjan(v,now);
                low[now]=min(low[now],low[v]);
                if(low[v]>=dfn[now])
                {
                    if(now==root) deg++;
                    else cut[now]=true;
                }
            }
            else if(v!=father) low[now]=min(low[now],dfn[v]);//不要跟求环混了 具体原理去网上找 
        }
}

void dfs(int x)//遍历每个连通块 
{
    vis[x]=T;//标记 
    if(cut[x]) return;
    cnt++;//数量 
    for(int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if(cut[v]&&vis[v]!=T) num++,vis[v]=T;//统计割点数目。 
        //如果是割点且标记不与遍历的的连通块相同就修改标记。 
        if(!vis[v])dfs(v);
    }
}

int main()
{
    m=read();
    while (m)
    {
        first();
        for (int i=1;i<=m;i++)
        {
            int u=read(),v=read();
            n=max(n,max(u,v));//这个地方要处理一下 
            insert(u,v); insert(v,u);
        }
        for (int i=1;i<=n;i++)
        {
            if (!dfn[i]) Tarjan(root=i,0);
            if (deg>=2) cut[root]=1;//根节点的割点 
            deg=0;//不要忘记是多组数据 
        }
        for (int i=1;i<=n;i++)
            if (!vis[i]&&!cut[i])//不是割点 
            {
                T++; cnt=num=0;//T为连通块的标记 
                dfs(i);
                if (!num) ans1+=2,ans2*=cnt*(cnt-1)/2;//建两个 别忘记除以二 因为两个建立的出口没有差异 
                if (num==1) ans1++,ans2*=cnt;//建一个 
            }
        printf("Case %d: %d %lld\n",++cases,ans1,ans2);
        m=read();
    }
    return 0;
}

 

posted @ 2017-02-07 16:21  安月冷  阅读(715)  评论(0编辑  收藏  举报