把每道题的 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;
}