其他分享
首页 > 其他分享> > 5739. 毒奶

5739. 毒奶

作者:互联网

Description

现实世界中有 \(n\) 个城市,编号为 \(1 \sim n\),城市之间有道路相连,道路是双向的,小 \(\mathrm{C}\) 可以从道路的任意一端走到另一端。因为交通并不发达,所以一些城市之间可能无法互相到达。

梦境和现实是非常相似的,所以幻想世界中也有 \(n\) 个城市,编号为 \(1^{\prime} \sim n^{\prime}\),还有一些双向道路。

小 \(\mathrm{C}\) 相信梦境和现实并没有本质的区别。她选择了一种方法,把梦境中的城市和现实中的城市一一对应起来,从而在两个世界对应的城市之间移动。具体地, 如果现实世界的城市 \(a\) 对应幻想世界的城市 \(b^{\prime}\) 那么小 \(\mathrm{C}\) 可以从 \(a\) 走到 \(b^{\prime}\) 或从 \(b^{\prime}\) 走到 \(a\)。因为幻想世界的交通也并不发达,所以两个世界中的道路共有恰好 \(n-1\) 条。

这样一来,小 C 周游世界的可行性就取决于对应方法的选取。定义一种对应方法是合法的,当且仅当这种对应方法使小 \(C\) 能够周游世界。

小 \(C\) 想知道,有多少种对应方法是合法的。两种对应方法不同,当且仅当存在一个现实世界的城市,在两种方法中对应不同的幻想世界的城市。

因为答案可能非常大,所以你只需要告诉她答案模 998244353 的值。

Solution

注意到 \(n\le 20\),这启示我们使用状压 DP。

初始情况下有 \(2n\) 个点,\(n-1\) 条边,所以会形成 \(n+1\) 个联通块。

用并查集记录每个联通块的大小。枚举每个状态,求出各状态下所包含的点的数量。

有一个比较显然的结论是,只有在左右两边联通块的总点数相同的情况下,才能在这两个联通块之间连边。求出总方案 \(g_s\)。

然后枚举每个状态(注意不要枚举无用状态),因为合法方案 = 总方案 - 非法方案,所以考虑如何得出非法方案。至于非法的情况就是在 \(s\) 这个状态下,\(t\) 是 \(s\) 的子集,而 \(t\) 所含有的连通块和 \(s^t\) 所含有的连通块各自连边,方案数是 \(g_t*f_{s \text{^} t}\)。所以只需要从 \(s\) 中将编号最小的连通块取出,得到状态 \(ss\) ,随后枚举 \(ss\) 的子集,转移即可。

Code

#include<cstdio>
#define N 21
#define mod 998244353
#define ll long long
using namespace std;
int n,m,x,y,tot,n1,n2,s1,s2,all,fi[N],size[N],sz1[1<<N],sz2[1<<N];
ll fac[N],g[1<<N],f[1<<N];
int find(int x)
{
    if (fi[x]!=x) fi[x]=find(fi[x]);
    return fi[x];
}
int main()
{
    freopen("milk.in","r",stdin);
    freopen("milk.out","w",stdout);
    scanf("%d%d",&n,&m);
    fac[0]=1;
    for (int i=1;i<=n;++i)
        fac[i]=fac[i-1]*(ll)i%mod;
    for (int i=1;i<=n;++i)
        fi[i]=i,size[i]=1;
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        x=find(x);y=find(y);
        if (x!=y) fi[y]=x,size[x]+=size[y];
    }
    for (int i=1;i<=n;++i)
        if (find(i)==i)
        {
            sz1[1<<tot]=size[i];
            ++tot;
        }
    n1=tot;tot=0;s1=(1<<n1)-1;
    for (int i=1;i<=n1;++i)
        for (int j=1;j<=s1;++j)
            if ((j>>(i-1))&1) sz1[j]+=sz1[j^(1<<(i-1))];
    for (int i=1;i<=n;++i)
        fi[i]=i,size[i]=1;
    for (int i=1;i<=n-1-m;++i)
    {
        scanf("%d%d",&x,&y);
        x=find(x);y=find(y);
        if (x!=y) fi[y]=x,size[x]+=size[y];
    }
    for (int i=1;i<=n;++i)
        if (find(i)==i)
        {
            sz2[1<<tot]=size[i];
            ++tot;
        }
    n2=tot;tot=0;s2=(1<<n2)-1;
    for (int i=1;i<=n2;++i)
        for (int j=1;j<=s2;++j)
            if((j>>(i-1))&1) sz2[j]+=sz2[j^(1<<(i-1))];
    all=(1<<(n1+n2))-1;
    if (n1+n2!=n+1) {printf("0\n");return 0;}
    for (int s=1;s<=all;++s)
    {
        int num1=sz1[s&s1],num2=sz2[s>>n1];
        if (num1==num2) g[s]=fac[num1];
    }
    for (int s=1;s<=all;++s)
    {
        if (!g[s]) continue;
        f[s]=g[s];
        int ss=s^(s&(-s));
        for (int t=ss;t;t=(t-1)&ss)
            f[s]=(f[s]-(ll)(g[t]*f[s^t])%mod+mod)%mod;
    }
    printf("%lld\n",f[all]);
    return 0;
}

标签:prime,毒奶,现实,城市,世界,枚举,5739,对应
来源: https://www.cnblogs.com/Livingston/p/16460099.html