1. 程式人生 > 實用技巧 >BZOJ-3444 最後的晚餐(並查集+組合計數)

BZOJ-3444 最後的晚餐(並查集+組合計數)

題目描述

  \(n(1\leq n\leq 5\times 10^5)\) 個人坐成一排,有 \(m(0\leq m\leq n)\) 個條件,第 \(i\) 個條件要求 \(a_i\)\(b_i\) 相鄰,求方案數。

分析

  把坐在一起看成無向邊,用並查集維護關係,如果某個點的度數大於 \(2\),無解,如果形成了邊數大於 \(3\) 的環,無解,可能有重邊,注意去重。答案為 \(2^{cnt}·tot!\),其中 \(cnt\) 為點數大於等於 \(2\) 的連通塊,\(tot\) 為總連通塊數量。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int mod=989381;
int fa[N],a[N],deg[N],cnt[N];
int get(int x)
{
    if(x!=fa[x])
        return fa[x]=get(fa[x]);
    return x;
}
void merge(int x,int y)
{
    int fx=get(x),fy=get(y);
    if(fx!=fy)
        fa[fx]=fy;
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d",&x);
        scanf("%d",&y);
        a[x]=y;
        if(a[a[x]]==x)//重邊
            continue;
        int fx=get(x),fy=get(y);
        if(fx==fy||deg[x]>=2||deg[y]>=2)
        {
            puts("0");
            return 0;
        }
        deg[x]++;
        deg[y]++;
        merge(x,y);
    }
    for(int i=1;i<=n;i++)
        cnt[get(i)]++;
    long long ans=1,tot=0;
    for(int i=1;i<=n;i++)
    {
        if(cnt[i]!=0)
        {
            if(cnt[i]>=2)
                ans=ans*2%mod;
            tot++;
        }
    }
    for(int i=1;i<=tot;i++)
        ans=ans*i%mod;
    cout<<ans<<endl;
    return 0;
}