Come OJ P3803 Farewell 容斥+快速子集变换
题意:给定 $n$ 个点,$m$ 条边的图,求有多少子图满足子图 (即边可以任意定方向或消失)是一个 DAG ?
$f(S)=\sum_{T \subseteq S} (-1)^{|T|-1} \binom{|S|}{|T|} f(S-T) 2^{E(S)-E(T)-E(S-T)}$.
这里 $E(S)$ 表示点集 $S$ 包含的边的数量.
直接套用这个式子,然后用 FWT 的 or 运算做一下子集卷积即可.
#include <bits/stdc++.h> #define N 23 #define ll long long #define mod 998244353 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int inv2,n,m,mp[N][N],bin[N],size[1<<N]; int E[1<<N],dp[N][1<<N],g[N][1<<N]; inline int qpow(int x,int y) { int tmp=1; for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) tmp=(ll)tmp*x%mod; return tmp; } inline int INV(int x) { return qpow(x,mod-2); } void FWT(int *a,int len,int opt) { for(int i=1;i<len;i<<=1) for(int j=0;j<len;j+=i<<1) for(int k=0;k<i;++k) { int x=a[j+k],y=a[j+k+i]; a[j+k]=(ll)(x+y)%mod,a[j+k+i]=(ll)(x-y+mod)%mod; if(opt==-1) a[j+k]=(ll)inv2*a[j+k]%mod,a[j+k+i]=(ll)inv2*a[j+k+i]%mod; } } int main() { // setIO("input"); inv2=INV(2); scanf("%d%d",&n,&m); for(int i=1;i<=n+1;++i) bin[i]=1<<(i-1); for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); mp[x][y]=mp[y][x]=1; } for(int S=1;S<bin[n+1];++S) { int fi; for(int j=1;j<=n;++j) if(S&bin[j]) { E[S]=E[S^bin[j]],fi=j; break; } size[S]=size[S^bin[fi]]+1; for(int j=1;j<=n;++j) if(mp[fi][j]&&(S&bin[j])) ++E[S]; } for(int i=1;i<bin[n+1];++i) { int d=(size[i]%2)==0?(mod-1):1; g[size[i]][i]=(ll)d*INV(qpow(2,E[i]))%mod; } dp[0][0]=1; FWT(dp[0],bin[n+1],1); for(int i=1;i<=n;++i) FWT(g[i],bin[n+1],1); for(int sz=1;sz<=n;++sz) { for(int i=1;i<=sz;++i) for(int s=0;s<bin[n+1];++s) (dp[sz][s]+=(ll)g[i][s]*dp[sz-i][s]%mod)%=mod; } FWT(dp[n],bin[n+1],-1); printf("%d\n",(ll)dp[n][bin[n+1]-1]*qpow(2,E[bin[n+1]-1])%mod*INV(qpow(3,E[bin[n+1]-1]))%mod); return 0; }