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; }