題目:
你現在需要設計一個密碼S,S需要滿足:
- S的長度是 N;
- S只包含小寫英文字母;
- S不包含子串 T;
請問共有多少種不同的密碼滿足要求?
由於答案會非常大,請輸出答案模 1e9+7的餘數。
輸入格式:
第一行輸入整數N,表示密碼的長度。
第二行輸入字串T,T中只包含小寫字母。
輸出格式:
輸出一個正整數,表示總方案數模 109+7後的結果。
資料範圍:
1≤N≤50,
1≤|T|≤N,|T|是T的長度。
分析:
關於是否包含子串的問題,可以使用kmp來嘗試實現,另外此題涉及狀態的轉移,我們定義f[i][j]為密碼設計到第i位時,T匹配到了第j位時的方案數,那麼下一個狀態f[i+1][t],t為透過kmp轉移到的下一個的狀態(T的下標),另外由於轉移得到的狀態與密碼i+1位的小寫字母是什麼有關,我們可以透過列舉26個小寫字母,計算出所有轉移後可能的狀態,一個狀態可能有多個不同的狀態轉移得到,因為求的是方案數,我們需要累加,所以得到狀態轉移方程位f[i+1][t] += f[i][j]。最終的答案是max(f[n][0~m-1]),m為不包含的子串的長度.
程式碼:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int mod = 1e9 + 7;
int n;
char s[55];
int ne[55];
int f[55][55];
int main(){
cin >> n >> s + 1;
int m = strlen(s + 1);
//求出ne陣列,用於狀態轉移
for(int i = 2, j = 0; i <= m; i ++){
while(j && s[i] != s[j + 1]) j = ne[j];
if(s[i] == s[j + 1])j ++;
ne[i] = j;
}
f[0][0] = 1;
for(int i = 0; i < n; i ++){
for(int j = 0; j < m; j ++){
for(char k = 'a'; k <= 'z'; k ++){
//列舉每一個先前狀態j和i位為k時,累加到轉移後的狀態。
int u = j;
while(u && k != s[u + 1])u = ne[u];
if(k == s[u + 1])u ++;
//因為密碼中不能包含子串T,所以u<m時,才可以轉移
if(u < m) f[i+1][u] = (f[i+1][u] + f[i][j]) % mod;
}
}
}
int res = 0;
for(int i = 0; i < m; i ++)res = (res + f[n][i]) % mod;
cout << res;
}