【题解】P2167 [SDOI2009]Bill的挑战(状压 DP)
【题解】P2167 [SDOI2009]Bill的挑战
挺好的一道状压 DP。可惜我脑子有病。()
题目链接
题意概述
给出 个长度相同的字符串(由小写英文字母和 ?
组成),,求与这 个串中的刚好 个串匹配的字符串 的个数,答案对 取模。
若字符串 和 匹配,满足以下条件:
\1. 。
\2. 对于任意的 ,满足 或者 。
其中 只包含小写英文字母。
数据范围
- 对于 的数据,,;
- 对于 的数据,,;
- 对于 的数据,,,。
思路分析
首先看到这个数据范围肯定想到的是状压,并且要状压的是选了哪些字符串。
那么定义 表示选择 这个状态的字符串的方案数是多少。
然后就会发现不太好转移,因为我们只能确定 这个状态构成的字符串对应的方案数是多少,而对于不同的 ,可能会有相同的一个字符串 满足条件,会算重。
那么我们考虑在状态中加入一维:定义 表示考虑到了字符串的第 位,当前选的状态是 时,方案数是多少。
考虑如何转移。
我们考虑对于一个状态 它能转移到哪儿去。
首先肯定是要转移到某一个 上去。那么 是什么呢。
这时候我们要用到一个辅助数组。
定义 表示的是考虑到了字符串的第 位,这一位上可以与 对应字符匹配的 有哪些。
考虑对于一个 ,这一位上是什么的时候可以与它匹配,比较显然的是要么这一位是 ?
要么是 对应的字符。
那么可以直接枚举 并枚举每一个字符串直接判断这一位是否与 匹配即可,可以预处理。
有了这个 数组,我们也就可以轻松的转移 了。
我们直接考虑下一位填每一个字符 a
到 z
时对应的方案数是多少即可。
初始状态:。
那么最后的答案就是对于每一个状态 二进制下 的个数恰好为 的所有的 之和。其中 表示每一个字符串的长度。
代码实现
//luoguP2167
#include<cstdio>
#include<iostream>
#include<cstring>
#define int long long
using namespace std;
const int mod=1000003;
const int maxn=16;
const int maxm=55;
string s[maxn];
int f[maxm][maxm],dp[maxm][1<<maxn],val[1<<maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main()
{
int T=read();
while(T--)
{
memset(dp,0,sizeof(dp));
memset(f,0,sizeof(f));
memset(val,0,sizeof(val));
int n,k;
n=read();k=read();
for(int i=0;i<n;i++)cin>>s[i],s[i]='%'+s[i];
int len=s[0].size()-1;
for(int sta=0;sta<(1<<n);sta++)
{
for(int i=0;i<n;i++)
{
if(sta&(1<<i))val[sta]++;
}
}
for(int i=1;i<=len;i++)
{
for(char ch='a';ch<='z';ch++)
{
for(int j=0;j<n;j++)
{
if(s[j][i]=='?'||s[j][i]==ch)f[i][ch-'a']|=(1<<j);
}
}
}
dp[0][(1<<n)-1]=1;
for(int i=0;i<len;i++)
{
for(int sta=0;sta<(1<<n);sta++)
{
if(!dp[i][sta])continue;
for(char ch='a';ch<='z';ch++)
{
(dp[i+1][sta&f[i+1][ch-'a']]+=dp[i][sta])%=mod;
}
}
}
int ans=0;
for(int sta=0;sta<(1<<n);sta++)
{
if(val[sta]==k)(ans+=dp[len][sta])%=mod;
}
cout<<ans<<'\n';
}
return 0;
}