KMP+狀態轉移

陈佳威發表於2024-07-17

題目:

你現在需要設計一個密碼S,S需要滿足:

  1. S的長度是 N;
  2. S只包含小寫英文字母;
  3. 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;
}

相關文章