[WC2018]州区划分(子集卷积)

传送门
首先我们可以列个dp方程出来:
d p S = ( 1 w S ) p ∑ T ⊆ S d p T ∗ ( w S − T ) p dp_S=({\frac 1 {w_{S}}})^p \sum_{T\subseteq S}dp_T*(w_{S-T})^p dpS=(wS1)pTSdpT(wST)p
然后这样枚举子集暴力dp是 O ( 3 n ) O(3^n) O(3n)
然后我们可以加一维变成子集卷积 d p i , S dp_{i,S} dpi,S
然后直接子集卷积做就好了
O ( n 2 2 n ) O(n^22^n) O(n22n)
这题不知道是卡常数还是我写的丑,反正就是最慢的点14s
顺带一提,这个题我还试了试刚刚学的FMT,看代码常数挺小,可是我还是这么慢qwq
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define LL long long
using namespace std;
inline int read(){
    int x=0,f=1;char ch=' ';
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
const int N=1<<21,mod=998244353;
int n,m,p,tot,L;
int head[N],to[N],Next[N];
int deg[N],fa[N],v[N],can[N];
int w[N],inv[N],dp[22][N],g[22][N];
inline void addedge(int x,int y){to[++tot]=y;Next[tot]=head[x];head[x]=tot;}
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline LL ksm(LL a,LL n){
    LL ans=1LL;
    while(n){
        if(n&1)ans=ans*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return ans;
}
inline bool check(int S){
    int cnt=0;
    for(int i=0;i<n;++i)if((S>>i)&1)fa[i]=i,++cnt,deg[i]=0,w[S]+=v[i];
    for(int i=0;i<n;++i){
        if(!((S>>i)&1))continue;
        for(int j=head[i];j;j=Next[j]){
            int u=to[j];
            if(!((S>>u)&1))continue;
            ++deg[i];++deg[u];
            int fi=find(i),fu=find(u);
            if(fi!=fu)fa[fi]=fu,--cnt;
        }
    }
    if(cnt>1)return true;
    cnt=0;
    for(int i=0;i<n;++i)if(((S>>i)&1) && (deg[i]&1))++cnt;
    if(cnt==0)return false;
    return true;
}
inline LL calc(LL x){
    if(!p)return 1;
    else if(p==1)return x%mod;
    else return x*x%mod;
}

inline void FMT(int *a){
    for(int i=1;i<L;i<<=1)
        for(int j=0;j<L;++j)
            if(i&j)a[j]=(a[j]+a[j^i])%mod;
}
inline void IFMT(int *a){
    for(int i=1;i<L;i<<=1)
        for(int j=0;j<L;++j)
            if(i&j)a[j]=(a[j]-a[j^i]+mod)%mod;
}
int main(){
    n=read();m=read();p=read();L=1<<n;
    for(int i=1;i<=m;++i){
        int x=read()-1,y=read()-1;
        addedge(x,y);
    }
    for(int i=0;i<n;++i)v[i]=read();
    for(int i=0;i<L;++i)if(check(i))can[i]=1;
    for(int i=0;i<L;++i)w[i]=calc(w[i]);
    for(int k=0;k<L;++k){
        int cnt=0;
        for(int i=0;i<n;++i)if((k>>i)&1)++cnt;
        if(can[k])g[cnt][k]=w[k];
        inv[k]=ksm(w[k],mod-2);
    }
    dp[0][0]=1;FMT(dp[0]);
    for(int i=0;i<=n;++i)FMT(g[i]);
    for(int i=1;i<=n;++i){
        for(int j=0;j<i;++j)
            for(int k=0;k<L;++k)
                dp[i][k]=(dp[i][k]+1LL*dp[j][k]*g[i-j][k])%mod;
        IFMT(dp[i]);
        for(int k=1;k<L;++k)dp[i][k]=1LL*dp[i][k]*inv[k]%mod;
        if(i==n)break;
        FMT(dp[i]);
    }
    printf("%lld",dp[n][L-1]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值