BZOJ 2730 [HNOI2012]矿场搭建 点双联通分量(割点)

题意: 链接.

方法: 点双联通分量(割点).

解析: 

首先这道题有两个问,并且是无向图
第一问是任意删掉一个点后,满足剩下所有的点均可以找到一个出口,则最少安放的出口数是多少?
第二问是满足最少安防的出口数有几种方案?
首先先把图画出来看第一个样例


我们发现他所选取的四个点分别都是把1删掉后,所处于两个不同的块中的两点,而1恰好为其中的割点,观察第二组样例后,依旧如此。
则第一问就可以转化为:删掉割点后有多少个块?
然而这并不是正确的,因为我们发现,如果一个块与两个割点相连,那么即使其中的一个割点是坍塌点,这个块仍旧能与另一个块组成在一起。所以所求的块应该是仅包含一个割点的块的个数。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std ;
struct node
{
    int from ;
    int to ;
    int next ;
};
node edge[1001] ;
int head[1001] ;
int deep[1001] ;
int low[1001] ;
int f[1001] ;
int belong[1001] ;
int v[1001] ;
int pt[1001] ;
int num[1001] ;
int cnt , tot , cnt_root , root , ans;
void init()
{
    memset(head , -1 , sizeof(head)) ;
    memset(deep , 0 , sizeof(deep)) ;
    memset(low , 0 , sizeof(low)) ;
    memset(f , 0 , sizeof(f)) ;
    memset(num , 0 , sizeof(num)) ;
    cnt = 1  , tot = 0 , ans = 0;
}
void init2()
{
    memset(deep , 0 , sizeof(deep)) ;
    memset(low , 0 , sizeof(low)) ;
    memset(belong , 0 , sizeof(belong)) ;
    memset(pt , 0 , sizeof(pt)) ;
    tot = 0 ;
}
void edgeadd(int from , int to)
{
    edge[cnt].from = from ;
    edge[cnt].to = to ;
    edge[cnt].next = head[from] ;
    head[from] = cnt ++ ;
}
void tarjan(int x , int fa)
{
    deep[x] = low[x] = ++tot ;
    for(int i = head[x] ; i != -1 ; i = edge[i].next)
    {
        int to = edge[i].to ;
        if(to == fa) continue ;
        if(!deep[to])
        {
            tarjan(to , x) ;
            low[x] = min(low[to] , low[x]) ;
            if(low[to] >= deep[x])
            {
                if(x == root) cnt_root ++ ;
                else f[x] ++ ;
            }
        }else low[x] = min(low[x] , deep[to]) ;
    }
}
void tarjan2(int x , int fa)
{
    deep[x] = low[x] = ++tot ;
    for(int i = head[x] ; i != -1 ; i = edge[i].next)
    {
        int to = edge[i].to ;
        if(to == fa || f[to]) continue ;
        if(!deep[to])
        {
            tarjan2(to , x) ;
            low[x] = min(low[to] , low[x]) ;
        }else low[x] = min(low[x] , deep[to]) ;
    }
    belong[x] = ans ;
    num[ans] ++ ;
}
int m , n;
int main()
{
    int tot = 0 ;
    while(scanf("%d" , &m) && m != 0)
    {
        tot ++ ;
        n = 0 ;
        init() ;
        for(int i = 1 ; i <= m ; i++)
        {
            int x , y ;
            scanf("%d%d" , &x , &y) ;
            edgeadd(x , y) ;
            edgeadd(y , x) ;
            n = max(x , n) ;
            n = max(y , n) ;
        }
        for(int i = 1 ; i <= n ; i++)
        {
            if(!deep[i])
            {
                cnt_root = 0 , root = i ;
                tarjan(i , 0) ;
                if(cnt_root >= 2) f[i] = cnt_root - 1 ;
            }
        }
        init2() ;
        for(int i = 1 ; i <= n ; i++)
        {
            if(!deep[i] && !f[i])
            {
                ans ++ ;
                tarjan2(i , 0) ;
            }
        }
        for(int i = 1 ; i <= n ; i++)
        {
            if(f[i])
            {
                memset(v , 0 , sizeof(v)) ;
                for(int j = head[i] ; j != -1 ; j = edge[j].next)
                {
                    int to = edge[j].to ;
                    if(!v[belong[to]])
                    {
                         pt[belong[to]] ++ ;
                         v[belong[to]] = 1 ;
                    }
                }
            }
        }
        long long tmp = 1 , tmp2 = 0;
        for(int i = 1 ; i <= ans ; i++)
        {
            if(pt[i] <= 1)
            {
                tmp *= num[i] ;
                tmp2 ++ ;
            }
        }
        if(ans == 1)
        {
            printf("Case %d: %d %d\n" , tot , 2 , n *(n-1) / 2) ;
        }else
        {
            printf("Case %d: %lld %lld\n" , tot , tmp2 , tmp) ;
        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值