做了两个状压dp,有点门路了
就是说一般状压dp都需要枚举前面的状态,然后利用插数的思想递推,一般想清楚转移就差不多了 ,它就是利用状态把等效的情况压在了一起,和位运算的优势来dp
这个题一开始老是想压每一个串的字符,但实际上2^50怎么也不行了,,
所以一定要看到15
既然是串数被压缩,那我们肯定不能枚举串了,我们应该想办法通过位置递推来完成压串的dp
对于每一个字符,我们需要知道每一个位置上能填的串的情况
这个题有一个匹配数,所以f存匹配的个数
注意到串的字符匹配位数有无序性(无论什么位置匹配,所有都匹配了就行)
所以就可以一并考虑,在每一个位置枚举所有可能填的字符,转移到下一阶段做起点
这个dp的独特之处就是利用字符的枚举构造T并同时筛掉每次枚举不合法的串
以前不合法的,之后一定不合法。
码:
#include<iostream>
#include<cstdio>
using namespace std;
#include<cstring>
#define P 1000003
int f[55][1<<16],pei[55][55],n,K,T,ans;
char str[55][55];
int main()
{
scanf("%d",&T);
while(T--)
{ ans=0;
memset(pei,0,sizeof(pei));
memset(f,0,sizeof(f));
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++)scanf("%s",str[i]+1);
int len=strlen(str[1]+1);
for(int i=1;i<=len;i++)
for(int j=1;j<=26;j++)
for(int k=1;k<=n;k++)
{
if(str[k][i]=='?'||str[k][i]-'a'+1==j)pei[i][j]=pei[i][j]|(1<<(k-1));
}
f[0][(1<<n)-1]=1;
for(int i=0;i<len;i++)
for(int k=0;k<=(1<<n)-1;k++)
if(f[i][k])
for(int j=1;j<=26;j++)
{ //cout<<"l";
f[i+1][pei[i+1][j]&k]=(f[i+1][pei[i+1][j]&k]+f[i][k])%P;
}
for(int i=0;i<=(1<<n)-1;i++)
{
int k=i,js=0;
while(k)
{
if(k&1==1)js++;
k>>=1;
}
if(js==K)ans+=f[len][i],ans%=P;
}
printf("%d\n",ans);
}
}