把每道题的 H
当作一个集合,那么合法的选定题目的顺序要求前面的题目是后面的题目的子集。
考虑动态规划,设 $f(S)$ 表示最后选定的题目集合是 $S$。
设一种题目集合的数量是 $c(S)$,计算一个 $w(S) = \sum_{i=1}^{c(S)} \operatorname{A}_{c(S)}^i$,表示至少选择一个此类题目的方案数。
那么有转移方程:
$$
f(S) = w(S) (1 + \sum_{T \subseteq S} f(T))
$$
这样做时间复杂度是 $O(3^m)$ 的。
我们考虑类似于高维前缀和的优化,但是直接修改是会超时的。
我们考虑一个折中的办法,设 $g(S,T)$ 表示前 $10$ 位集合为 $S$,后 $10$ 位集合为 $T$ 的子集的 $f$ 值之和。
查询 $(S,T)$ 的时候遍历 $S$ 的子集 $P$,对 $g(P,T)$ 求和。
修改 $(S,T)$ 的时候遍历 $T \subseteq P$,修改 $g(S,P)$。
那么我们可以在 $O(2^{10})$ 的时间复杂度内完成修改与查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include<bits/stdc++.h> using namespace std; const int maxn=1e5; const int maxm=1<<20; const int maxs=1<<10; const int mod=1e9+7; int n,m; int a[maxn+5]; int c[maxm+5]; int w[maxm+5]; int f[maxm+5]; int g[maxs+5][maxs+5]; int fac[maxn+5],inv[maxn+5]; int ans; inline int Get(){ char ch=getchar(); while(!(ch=='E'||ch=='H')) ch=getchar(); return ch=='H'; } inline int Fpow(int x,int y){ int res=1; for(;y;x=1ll*x*x%mod,y>>=1) if(y&1) res=1ll*res*x%mod; return res; } inline int A(int x,int y){ return 1ll*fac[x]*inv[x-y]%mod; } inline void Init(){ fac[0]=1; for(int i=1;i<=maxn;i++) fac[i]=1ll*fac[i-1]*i%mod; inv[maxn]=Fpow(fac[maxn],mod-2); for(int i=maxn-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod; } signed main(){ Init(); scanf("%d%d",&n,&m); for(int i=0;i<m;i++) for(int j=1;j<=n;j++) a[j]|=Get()<<i; for(int i=1;i<=n;i++) c[a[i]]++; for(int i=0;i<1<<m;i++) for(int j=1;j<=c[i];j++) w[i]=(w[i]+A(c[i],j))%mod; for(int i=0;i<1<<m;i++){ if(!c[i]) continue; int A=i>>10,B=i&1023; int res=1; for(int j=0;j<1024;j++) if((j&A)==j) res=(res+g[j][B])%mod; f[i]=1ll*w[i]*res%mod; for(int j=0;j<1024;j++) if((j&B)==B) g[A][j]=(g[A][j]+f[i])%mod; ans=(ans+f[i])%mod; } printf("%d\n",ans); return 0; }
|