洛谷P3225 [HNOI2012]矿场搭建 割点 tarjan 点双

题目链接:https://www.luogu.com.cn/problem/P3225

解决这道题目之前,先说明两个知识点。
什么是点双?点双即点双连通分量,在一个连通图里,任意点到其他点都有两条点不重复路径,这就被称为点双连通分量,仅有一条边为特殊的点双。
什么是割点?割点就是在一个连通图里,删去这个点后,连通分支增多的那个点。可以用tarjan来找。
不同点双至多只有一个公共点,并且是割点,每个割点至少是两个点双的公共点。

说完知识点,再看这题,本题可以分成三种情况。
情况一
点双没有割点,要建两个出口,一个出口塌了还可以跑另一个出口,方案数有 C2n(n为该点双里的点数,除割点外) 种,即n(n-1)/2。
在这里插入图片描述

情况二
点双有一个割点(红色为割点)(图是上下两个点双),要建一个出口,因为这个出口塌了,可以通过割点跑到另一个点双的那个出口。方案数有 n 种。
在这里插入图片描述
情况三
点双有至少两个割点(红色为割点),图中为上中下三个点双,不用建出口,因为一个割点塌了,还可以通过另一个割点跑到其它点双的出口。
在这里插入图片描述

我们用tarjan找完割点之后,搜索每一个点双,统计它所含的割点数目,除割点外其它点的数目,更新答案即可,另外多组数据,别忘记初始化。

代码如下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=505;
int head[maxn],dfn[maxn],low[maxn];
int col[maxn];//每个点的颜色
bool cut[maxn];//标记是否为割点
int top,cnt,deg;
int ans1;//答案一
int dfn_num;
int root;//根
int s;//一个连通块除割点外的数量
int sum;//一个连通块割点的数量
int col_id;//颜色编号
ll ans2;//答案二
int n,m;
struct node
{
    int to,next;
}edge[maxn*2];//无向边
void add(int x,int y)//加边
{
    edge[++cnt].next=head[x];
    edge[cnt].to=y;
    head[x]=cnt;
}
void init()//初始化
{
    top=cnt=deg=ans1=dfn_num=0,n=0,col_id=0;
    ans2=1;
    memset(cut,0,sizeof(cut));
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(col,0,sizeof(col));
}
void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++dfn_num;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(!dfn[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])
            {
                if(u==root)
                deg++;
                else
                cut[u]=1;
            }
        }
        else if(v!=fa)
        low[u]=min(low[u],dfn[v]);
    }
}
void dfs(int u)//遍历每个连通块
{
    col[u]=col_id;//染色
    if(cut[u])
    return ;
    s++;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(cut[v]&&col[v]!=col_id)
        {
            sum++;//割点数目+1
            col[v]=col_id;
            //将该割点染为该连通块的颜色
        }
        if(!col[v])
        dfs(v);
    }
}
int main()
{
    int t=0;
    while(~scanf("%d",&m)&&m)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            n=max(n,max(x,y));
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])
            {
                root=i;
                tarjan(i,0);
            }
            if(deg>=2)//根节点有两个孩子就是割点
            cut[root]=1;
            deg=0;
        }
        //puts("1");
        for(int i=1;i<=n;i++)
        {
            if(!col[i]&&!cut[i])//不是割点且未被染色
            {
                col_id++,s=sum=0;
                dfs(i);
                if(!sum)//没割点
                ans1+=2,ans2*=s*(s-1)/2;//建两个
                if(sum==1)//一个割点
                ans1++,ans2*=s;//建一个
                //两个割点,不用建
            }
        }
        printf("Case %d: %d %lld\n",++t,ans1,ans2);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44491423/article/details/104574382
今日推荐