【题解】[SDOI2009]Bill的挑战
\(\text{Solution:}\)
最初的 naive 想法是直接枚举子集算出每个子集中全部匹配的对应 \(T\) 串的个数,但是脑残到最后写完才反应过来会算重……
但好像用二项式反演还能搞回来(雾)
考虑 dp : 设 \(dp[i][j]\) 表示填充好 \([1,i]\) 的长度,能匹配的子集状态是 \(j\) 的方案数。
那么可以考虑用刷表法,枚举 \(i+1\) 位置填什么。一共 \(26\) 种组合。所以可以预处理出 \(i\) 位置能匹配字符 \(j\) 的状态集合,转移就可以是:
\[\text{dp[i+1][j&state]+=dp[i][j]}
\]
就是对能匹配的集合求交集。
于是这样的方案数一定是不重复的,因为每一次填上位置的字符都不同。
不是说匹配到的字符串不同,而是我们自己构造出的 \(T\) 是不同的。
重点在于让你每次构造出来的 \(T\) 都是不同的,这样才会保证不重不漏。
从这角度出发,进而启发我们对字符串 \(T\) 进行 \(dp\),状态是和 \(T\) 息息相关的。
重点在于要反思设计状态后注意一定不能把状态给算重复了,一定要考虑最容易算重的是什么。
这个时候往往可以考虑一下状态对它设计,这样更有利于我们对它去重计数的处理。
傻逼错误:数组开小了,调了半天,下次写代码就直接把数组拉到最不可能出错的大小就行了。
#include<bits/stdc++.h>
using namespace std;
const int mod=1000003;
inline int Mod(int x) {
return (x>=mod?(x-mod):(x<0?(x+mod):x));
}
inline int Max(int x,int y) {
return x>y?x:y;
}
inline int Min(int x,int y) {
return x<y?x:y;
}
inline int Add(int x,int y) {
return Mod(x+y);
}
inline int Mul(int x,int y) {
return 1ll*Mod(x)*Mod(y)%mod;
}
int T,n,dp[51][1<<17],len,K;
int match[51][26];
char A[17][60];
inline int pop_count(int x) {
int res=0;
while(x!=0) {
res+=x&1;
x>>=1;
}
return res;
}
inline void Clear() {
memset(dp,0,sizeof dp);
memset(match,0,sizeof match);
}
int main() {
freopen("111.txt","r",stdin);
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&K);
Clear();
for(int i=1; i<=n; ++i) {
scanf("%s",A[i]+1);
}
len=strlen(A[1]+1);
int w=(1<<n)-1;
dp[0][w]=1;
for(int i=1; i<=len; ++i) {
for(int j=0; j<26; j++) {
for(int k=1; k<=n; k++) {
if(A[k][i]=='?'||A[k][i]-'a'==j) {
match[i][j]|=(1<<(k-1));
}
}
}
}
for(int i=0; i<len; ++i) {
for(int j=0; j<(1<<n); ++j) {
for(int k=0; k<26; ++k) {
int state=j&match[i+1][k];
dp[i+1][state]+=dp[i][j];
if(dp[i+1][state]>=mod)dp[i+1][state]-=mod;
}
}
}
int ans=0;
for(int i=0; i<(1<<n); ++i) {
if(__builtin_popcount(i)==K) {
ans=ans+dp[len][i];
if(ans>=mod)ans-=mod;
}
}
cout<<ans<<endl;
}
return 0;
}