[WC2018]州区划分

题目要求的: [ ∑ [ 合法 ](ki=1xViwxij=1xVjwx)pmod998244353 ] ( ∏ i = 1 k ∑ x ∈ V i w x ∑ j = 1 i ∑ x ∈ V j w x ) p mod 998244353

合法条件: |Vi|=n,|Vi|=n,Vi ∑ | V i | = n , | ⋃ V i | = n , V i 没有欧拉回路

考虑一个 DP,fs=tsfstgt(valtvals)p D P , f s = ∑ t ∈ s f s − t g t ( v a l t v a l s ) p

fs f s 是当前划分集合是 s s 的答案,gs表示集合 s s 是否合法,vals就是 s s 的权值和

可以看出这个DP 3n 3 n (枚举子集)的

发现这个玩意 tsfstgt ∑ t ∈ s f s − t g t 是一个子集卷积(不会的转 2015vfk 2015 v f k 集训队论文)

fs=ab=s,ab=fagb(valbvals)p f s = ∑ a ⋃ b = s , a ⋂ b = ∅ f a g b ( v a l b v a l s ) p

并集可以直接 FMT F M T 然后点乘得到,交集就不好玩了

考虑 ab=|a|+|b|=|s| a ⋂ b = ∅ ⇒ | a | + | b | = | s |

这样就可以把交集为空的限制去掉了

fi,s f i , s 表示 s s 1的个数是 i i 的答案,gi,s=[s合法 ]vals ] ∗ v a l s

fi,sgi,s=i1j=0tsfj,stgij,t ⇒ f i , s g i , s = ∑ j = 0 i − 1 ∑ t ∈ s f j , s − t g i − j , t

直接把 fj,gijFMT f j , g i − j F M T 一下再点乘一下再 IFMT I F M T 一下得到 fi,sgi,s f i , s g i , s

再乘以 gi,s g i , s 的逆元就可以得到 fi,s f i , s

最后的答案就是 fn,2n1 f n , 2 n − 1

其他的看代码把(参考了一下 immortalCO i m m o r t a l C O 大佬的代码)

代码里那个怎么判断是不是欧拉回路主要是两点

①:一个点到当前集合的点的数量的奇数个

②:从一点出发,用状压模拟 dfs d f s ,看看能不能走完这个点集

感觉好妙啊, %%%immortalCO % % % i m m o r t a l C O

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(i,u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=22,M=1<<21,P=998244353;
typedef int arr1[N];
typedef int arr2[M];
int n,m,k,S;arr1 e,Mi;arr2 w,g[N],f[N],nx,cnt,pos,iw;long long tp[M];
inline int inv(int x){return x^1?P-1ll*(P/x)*inv(P%x)%P:1;}
inline int fpm(int a){int x=1;for(int b=k;b;b>>=1,a=1ll*a*a%P)if(b&1)x=1ll*x*a%P;return x;}
inline int add(int a,int b){return a+=b,a>=P?a-P:a;}
inline int sub(int a,int b){return a-=b,a<0?a+P:a;}
inline void fmt(int*a,int op(int,int)){
    fp(i,1,n)fp(s,1,S)if(s&Mi[i])
        a[s]=op(a[s],a[s^Mi[i]]);
}
int main(){
    #ifndef ONLINE_JUDGE
        file("walk");
    #endif
    sd(n),sd(m),sd(k);int u,v;
    pos[Mi[1]=1]=1;fp(i,2,n)pos[Mi[i]=Mi[i-1]<<1]=i;
    while(m--)
        sd(u),sd(v),
        e[u]|=Mi[v],e[v]|=Mi[u];
    fp(i,1,n)sd(w[Mi[i]]);S=(1<<n)-1;
    fp(i,1,S)nx[i]=i&-i,cnt[i]=cnt[i^nx[i]]+1;
    fp(s,1,S){
        w[s]=w[nx[s]]+w[s^nx[s]],iw[s]=inv(fpm(w[s]));
        int t=nx[s];
        for(int i=s;i;i-=nx[i])
            if(cnt[e[pos[nx[i]]]&s]&1)goto NotEuler;
        for(int i=nx[s],p;i;)
            p=pos[nx[i]],i-=nx[i],
            i|=e[p]&s&~t,t|=e[p]&s;
        if(s==t)continue;
        NotEuler:g[cnt[s]][s]=fpm(w[s]);
    }f[0][0]=1;fmt(f[0],add);int*a,*b;
    fp(i,1,n){
        fmt(g[i],add);memset(tp,0,8*S+8);
        fp(j,0,i-1){
            a=f[j],b=g[i-j];
            fp(s,0,S)tp[s]+=1ll*a[s]*b[s];
        }a=f[i];
        fp(s,0,S)a[s]=tp[s]%P;
        fmt(a,sub);
        fp(s,0,S)a[s]=1ll*a[s]*iw[s]%P;
        if(i<n)fmt(a,add);
    }
    printf("%d",f[n][S]);
return Ot(),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值