[JZOJ5739]毒奶 子集DP

26 篇文章 0 订阅
6 篇文章 0 订阅

这里讲的是 O(3nn) O ( 3 n ⋅ n ) 的可以过的暴力,正解不会。。。
把问题转化成给了 n n 个白点,n个黑点,给定了 n1 n − 1 条白白边和黑黑边,求再填 n n 条黑白边使之联通的方案数。
先用给定的边缩点,记下缩点后每个大点的size,然后随意钦定一个白点当根,剩下就是要求一层黑一层白的填,直接子集DP即可。
具体地,设 FS,c,i F S , c , i 表示当前已经选了 S S 中的点,当前层颜色是c,伸向下一层的边有 i i 条。只要枚举S的一个子集 T T ,且T中元素颜色全为 c c ,记T中元素个数是 cnt c n t size s i z e 的和是 tot t o t ,那么就有转移:

FS,c,totcntFST,!c,cntcnt! F S , c , t o t − c n t ← F S − T , ! c , c n t ∗ c n t !

最后答案还要乘上 irootsizei ∏ i ≠ r o o t s i z e i ,因为一个大点中每个点都可以作为连接上一层父亲的点。
还要减减枝,比如说一种颜色已经选完之后,下一层要立即选择全集什么的。。。就可以水过了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 45
#define ll long long
#define up(x,y) (x=(x+(y))%mod)
using namespace std;
const int mod=998244353;
int n,m,p[N],fa[N],e[N][2],sz[N],pc[1050000],tot[1050000];
ll f[1050000][2][21],ans,fac[N];
int getfa(int v)
{
    if(fa[v]==v) return v;
    return (fa[v]=getfa(fa[v]));
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d",&e[i][0],&e[i][1]);
    for(int i=m+1;i<n;i++)
    {
        scanf("%d%d",&e[i][0],&e[i][1]);
        e[i][0]+=n;e[i][1]+=n;
    }
    fac[0]=1;
    for(int i=1;i<=n;i++)
        fac[i]=fac[i-1]*i%mod;
    for(int i=1;i<=(n<<1);i++)
        fa[i]=i;    
    for(int i=1;i<n;i++)
        if(getfa(e[i][0])!=getfa(e[i][1])) fa[fa[e[i][0]]]=fa[e[i][1]];
    for(int i=1;i<=(n<<1);i++)
        sz[getfa(i)]++;
    int cnt=0;  
    for(int i=1;i<=n;i++)
        if(getfa(i)==i) sz[++cnt]=sz[i];
    m=cnt;
    for(int i=n+1;i<=(n<<1);i++)
        if(getfa(i)==i) sz[++cnt]=sz[i];
    n=cnt;      

    f[0][1][sz[n]]=1;n--;   
    int R[2];R[0]=((1<<m)-1),R[1]=R[0]^((1<<n)-1);  
    for(int s=0;s<(1<<n);s++)
        for(int i=1;i<=n;i++)
            if((s>>(i-1))&1) pc[s]++,tot[s]+=sz[i];
    for(int s=0;s<(1<<n);s++)
    {
        int u[2];u[0]=s&R[0],u[1]=s&R[1];
        for(int c=0;c<=1;c++)
        {
            if(u[c^1]==R[c^1]&&s+1!=(1<<n)) continue;
            for(int t=u[c];t;t=(t-1)&u[c])
                if(f[s^t][c^1][pc[t]])
                    up(f[s][c][tot[t]-pc[t]],f[s^t][c^1][pc[t]]*fac[pc[t]]);
        }
    }
    for(int c=0;c<=1;c++)
        up(ans,f[(1<<n)-1][c][0]);
    for(int i=1;i<=n;i++)
        ans=ans*sz[i]%mod;      
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值