SP13106 KOSARE - KOSARE

· · 题解

子集反演例题,这里补充一下子集反演的式子吧:

\text{let }F_S=\sum_{T\subset S} G_T \text{then }G_S=\sum_{T\subset S} (-1)^{\left|S-T\right|} F_T

可以用 FWT 实现。

题目大意

题目传送门:Link to Luogu。

给定 n 个集合,全集共有 m 个元素。求选择一些集合使其并起来是全集的方案数。

题目分析

但是状压 DP 貌似很难实现? 设 $f_S$ 表示集合为 $S$ **的子集**的集合个数。那么 $f_S$ 是可以直接高维前缀和求出来的,FWT 或一下即可。 考虑 $f_S$ 和答案有什么关系? 设 $F_S$ 表示选择的集合的并集为 $S$ **的子集**的方案数,那么只要这个集合是 $S$ 的子集,就可以选(也可以不选,要记得去掉都不选的情况),因此 $F_S=2^{f_S}-1$。 要求的是选择的集合的并集恰好为集合 $S$ 的方案数,子集反演一下即可。 复杂度 $\mathcal O(m\times 2^m+n)$。 ## 代码 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int max_n=2000006,mod=1000000007; inline int read(){ int x=0;bool w=0;char c=getchar(); while(c<'0' || c>'9') w|=c=='-',c=getchar(); while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return w?-x:x; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10^48); } inline int mi(int a,int p=mod-2){ int res=1; while(p){ if(p&1) res*=a,res%=mod; a*=a,a%=mod,p>>=1; } return res; } int n,m,S,f[max_n],F[max_n],ans; inline void OR(int *f,int x=1){ for(register int k=1,l=2;l<=S;l<<=1,k<<=1) for(register int i=0;i<S;i+=l) for(register int j=0;j<k;++j) f[i+j+k]+=f[i+j]*x%mod, f[i+j+k]=(f[i+j+k]+mod)%mod; } signed main(){ n=read(),m=read(),S=(1<<m); for(register int i=1;i<=n;++i){ int k=read(),st=0; for(register int i=1;i<=k;++i){ int p=read()-1; st|=(1<<p); } ++f[st]; } OR(f); for(register int i=0;i<S;++i) F[i]=mi(2,f[i])-1; OR(F,-1); write(F[S-1]); return 0; } ```