[BZOJ]2730: [HNOI2012]矿场搭建 Tarjan求割点

Description

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

题解:

求出哪些是割点后分类讨论咯。1、没有割点,那么至少设置2个,方案数为 C2n (这个显然)。2、有割点,那就看一下由割点分开的连通块,若一个连通块只经过一个割点,那么这个连通块内必须设置1个出口(否则割点出事故之后就没有出口),方案数乘上这个连通块内的点数;否则不用设置出口,因为任意一点出事故这个连通块还是可以连向其它。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
const int Maxn=1010;
const int Maxm=2010;
struct Edge{int y,next;}e[Maxm];
int last[Maxn],len;
int n,dfn[Maxn],low[Maxn],id,cnt,sta[Maxn],top;
bool cut[Maxn],in[Maxn];
void ins(int x,int y)
{
    int t=++len;
    e[t].y=y;e[t].next=last[x];last[x]=t;
}
void Tarjan(int x,int fa)
{
    int ch=0;
    low[x]=dfn[x]=++id;
    sta[++top]=x;in[x]=true;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y==fa)continue;
        if(!dfn[y])
        {
            Tarjan(y,x);
            ch++;
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])cnt++,cut[x]=true;//这种情况说明y无法回到x祖先,所以若x不为根,x为割点 
        }
        else if(in[y])low[x]=min(low[x],dfn[y]);
    }
    if(!fa&&ch==1)cnt--,cut[x]=false;//处理x为根的情况 
    if(low[x]==dfn[x])
    {
        int i;
        do
        {
            i=sta[top--];
            in[i]=false;
        }while(i!=x);
    }
}
LL size=0;
void dfs(int x,int ti)
{
    dfn[x]=1;size++;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(cut[y]&&dfn[y]!=ti)dfn[y]=ti,cnt++;
        else if(!dfn[y])dfs(y,ti);
    }
}
int main()
{
    int Case=0;
    while(1)
    {
        scanf("%d",&n);
        if(!n)break;Case++;
        memset(last,0,sizeof(last));len=cnt=0;
        memset(dfn,0,sizeof(dfn));id=0;
        memset(low,0,sizeof(low));
        memset(cut,false,sizeof(cut));
        memset(in,false,sizeof(in));top=0;
        int nn=0;
        for(int i=1;i<=n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            ins(x,y);ins(y,x);
            nn=max(nn,max(x,y));
        }
        for(int i=1;i<=nn;i++)
        if(!dfn[i])Tarjan(i,0);
        int ans1=0;LL ans2=1LL;
        if(!cnt)ans1=2,ans2=nn*(nn-1)/2;
        else
        {
            memset(dfn,0,sizeof(dfn));
            for(int i=1;i<=nn;i++)
            if(!dfn[i]&&!cut[i])
            {
                size=cnt=0;
                dfs(i,i);
                if(cnt==1)ans1++,ans2*=size;
            }
        }
        printf("Case %d: %d %lld\n",Case,ans1,ans2);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值