1.考虑状压的方式。ios
方案1:若是咱们把每个字符串压起来,用一个布尔数组表示与每个字母的匹配关系,那么空间为26^50,爆内存;数组
方案2:把每个串压起来,多开一维记录匹配字符,那么空间为nlen26,合法,但不便于状态的设计和转移;spa
方案3:把每个串同一个位置的字符放在一块儿,用一个布尔数组记录与每个小写字母的匹配关系,那么空间为26^15*len,爆内存;设计
方案4:把每个串同一个位置的字符压起来,用多开一维的整形数组记录与每个小写字母的匹配关系,空间为2^15*len,合法;code
采用方案4,那么关系具体的记录方式就是,开一个压缩数组r[2^15][len],r[i][j]表示全部串第i位与第j个小写字母的匹配状况:blog
例如,第1到n个串的第i位分别为:?,a, b,c,那么他们与'a'的匹配状况为r[i][0]=0011;ip
void init(){ for(R int i=0;i<len;++i)//第i列 for(R int x=0;x<26;++x){//对'a'增量为x r[i][x]=0; for(R int k=0;k<n;++k)//第k个串 if(s[k][i]=='?'||s[k][i]=='a'+x)r[i][x]|=(1<<k); } }
2.考虑DP的过程内存
方案1:f[i][j]表示当前匹配长度位i,状态为j,实际上也就是符合要求的匹配状况为j时的方案数ci
方案2:f[i][j]表示当前匹配到第i位,状态为j时的方案数;字符串
两种方案都可,只是第一种的i永远比第二种的i多1罢了。
可是咱们要采用方案1,由于初始化时,未匹配状况应该只有一个那就是111...111,因此对于第一种状况初始化就很是简单,也就是f[0][(1<<n)-1]=1;
而后就是转移,咱们发现当且仅当前一位时当前状态合法才可转移,咱们转移采用扩展状态的方式,即在当前状态上枚举增长状态,因此状态转移方程是:
(f[i][j&r[i-1][x]]+=f[i-1][j])%=mod;
#include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define R register typedef long long ll; using namespace std; const int mod=1e6+3; string s[20]; int n,m,r[51][50010],f[51][50010]; inline int lowbit(int x){return x&-x;} void init(){ cin>>n>>m; memset(f,0,sizeof(f)); for(R int i=0;i<n;++i)cin>>s[i]; int len=s[0].size(); for(R int i=0;i<len;++i)//第i列 for(R int x=0;x<26;++x){//对'a'增量为x r[i][x]=0; for(R int k=0;k<n;++k)//第k个串 if(s[k][i]=='?'||s[k][i]=='a'+x)r[i][x]|=(1<<k); } } void work(){ int lim=(1<<n)-1; int len=s[0].size(); f[0][lim]=1; for(R int i=1;i<=len;++i) for(R int j=0;j<=lim;++j)//状态 if(f[i-1][j])//若是当前前一列子集被更新过,即要扩展的状态合法 for(R int x=0;x<26;++x)//对'a'增量为x (f[i][j&r[i-1][x]]+=f[i-1][j])%=mod; int ans=0; for(R int i=0;i<=lim;++i){ int temp=i,cnt=0; while(temp){cnt++;temp-=lowbit(temp);} if(cnt==m) (ans+=f[len][i])%=mod; } printf("%d\n",ans); } int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ init(); work(); } return 0; }