1. 程式人生 > >[聯合集訓6-15]相互再歸的鵝媽媽 數位DP+斯特林反演

[聯合集訓6-15]相互再歸的鵝媽媽 數位DP+斯特林反演

問題要求無序方案數,可以轉化成求有序方案數再除以n!即可。
先考慮去掉互不相同的限制,最後用斯特林數容斥掉即可。
可以發現從高往低掃,假如出現R有一位是1,而且有一個數這位填了0,那麼剩下的數就可以再R的範圍內隨便填,因為最後都可以通過這個數把異或和調成0。於是我們可以通過列舉是哪一位最初發生了這種情況,求出g(i)表示選出i個數異或和為0的方案數。
那麼接下來可以列舉一個n的集合劃分,劃在同一個集合裡的數必須相同,不同集合之間沒有要求(也就是相同的至少是這個集合劃分),假設集合大小分別為a1,a2,...,ak,那麼要使其異或和為0的方案數就是

g(i=1k2ai),因為偶數大小集合沒有貢獻,根據斯特林反演,其容斥係數就為i=1k[1ai]=i=1k(1)ai1(ai1)!,分別考慮每個集合的容斥係數乘起來不難證明。

程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#define M 5000010
#define N 8
#define ll long long
#define up(x,y) x=(x+(y))%mod
using namespace std;
const int
mod=1000000007; int n,m,K,w[M],p2[M][N],pw[M][N]; ll g[N],C[N][N],f[N],fac[N],ans; char s[50010]; bool a[M]; ll ksm(ll a,ll b) { ll r=1; for(;b;b>>=1,a=a*a%mod) if(b&1) r=r*a%mod; return r; } void find(int num,int v) { if(v>n) { ll c=1,cnt=0; for(int i=1
;i<=num;i++) c=c*fac[f[i]-1]*((f[i]&1)?1:-1)%mod; for(int i=1;i<=num;i++) if(f[i]&1) cnt++; else c=c*w[1]%mod; up(ans,c*g[cnt]); return ; } for(int i=1;i<=num+1;i++) f[i]++,find(max(num,i),v+1),f[i]--; } int main() { scanf("%d%d%s",&n,&K,s+1); m=strlen(s+1); for(int i=0;i<K;i++) for(int j=1;j<=m;j++) a[i*m+j]=s[j]-'0'; m*=K; for(int i=m,mi=1;i;i--,mi=(mi<<1)%mod) w[i]=(w[i+1]+mi*a[i])%mod; fac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; C[0][0]=1; for(int i=1;i<=n;C[i][0]=1,i++) for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; bool flag=0; g[0]=1; for(int i=0,tmp=1;i<=m+1;i++,tmp=(tmp<<1)%mod) { p2[i][0]=pw[i][0]=1; for(int j=1;j<=n;j++) p2[i][j]=(ll)tmp*p2[i][j-1]%mod,pw[i][j]=(ll)w[i]*pw[i][j-1]%mod; } for(int i=1;i<=m;flag|=a[i],i++) if(a[i]) for(int j=1;j<=n;j++) if(!flag||!(j&1)) for(int k=0;k<=((j-1)>>1);k++) up(g[j],C[j][k<<1]*pw[i+1][k<<1]%mod*p2[m-i][j-(k<<1)-1]); find(0,1); printf("%lld",(ans+mod)*ksm(fac[n],mod-2)%mod); return 0; }