洛谷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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章