【題解】[SDOI2009]Bill的挑戰
阿新 • • 發佈:2021-07-12
\(\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; }