动态规划系列 | 状态机模型(下)| IndeedTokyo2019校招笔试题

1052. 设计密码

    • 1052. 设计密码
      • 题目描述
        • 输入格式
        • 输出格式
        • 数据范围
        • 样例
          • 输入样例 1
          • 输出样例 1
          • 输入样例 2
          • 输出样例 2
      • 问题分析
      • 复杂度分析
      • 程序代码

1052. 设计密码

题目描述

原题链接

你现在需要设计一个密码 S,S 需要满足:

  • S 的长度是 N;
  • S 只包含小写英文字母;
  • S 不包含子串 T;

例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串。

请问共有多少种不同的密码满足要求?

由于答案会非常大,请输出答案模 1 0 9 + 7 10^9+7 109+7 的余数。

输入格式

第一行输入整数 N,表示密码的长度。

第二行输入字符串 T,T 中只包含小写字母。

输出格式

输出一个正整数,表示总方案数模 1 0 9 + 7 10^9+7 109+7 后的结果。

数据范围

1 ≤ N ≤ 50 1≤N≤50 1N50
1 ≤ ∣ T ∣ ≤ N 1≤|T|≤N 1TN ∣ T ∣ |T| T 是 T 的长度。

样例
输入样例 1
2
a
输出样例 1
625
输入样例 2
4
cbc
输出样例 2
456924

问题分析

状态定义dp[i][j]:密码长度为i其后缀与模式串匹配的最大长度为j 的方案总数。

状态转移:借助 KMP 算法的 next 数组实现。若当前密码长度为 i-1,处于状态 j。在当前密码后面加上一个字符c,使得新的密码长度为i密码由长度为i-1变为i,需要重新计算当前的所处状态。使用 KMP 算法更新新密码的后缀与模式串匹配的最大长度,记为ptr此时dp[i][ptr]可以由dp[i-1][j]转移过来,密码长度为i,处于状态ptr的总方案数需要加上dp[i-1][j]

初始状态dp[0][0],即密码为空,且处于状态 0,即后缀与模式串匹配的最大长度为 0 的方案总数为 1.

第 i 轮的部分状态转移示意图如下,其中节点中的数字j表示结点的状态,即密码长度为i时,密码后缀与模式串匹配的最大长度为j

动态规划系列 | 状态机模型(下)| IndeedTokyo2019校招笔试题_第1张图片

最终的结果值 ∑ i = 0 m − 1 d p [ n ] [ i ] \sum_{i=0}^{m-1} dp[n][i] i=0m1dp[n][i],即对所有密码长度为 n,且状态不处于m的所有方案总数进行求和。

注意:状态转移过程中,不计算状态 m 的方案数,这样最终的总方案数将不包括 S 包含子串 T 的情况。

复杂度分析

时间复杂度: O ( 26 N 3 ) O(26N^3) O(26N3)

程序代码

#include 
#include 
#include 
using namespace std;

const int MOD = 1e9+7;

int main()
{
    int n;
    cin >> n;
    string t;
    cin >> t;
    int m = t.size();
    // 让子串下标从1开始
    t = " " + t;
    
    vector<int> next(m + 1, 0);
    // 计算next数组
    for(int i = 2, j = 0; i < t.size(); i++) {
        while( j && t[i] != t[j+1] ) {
            j = next[j];
        }
        if( t[i] == t[j+1] )  j++;
        next[i] = j;
    }
    
    vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
    dp[0][0] = 1;  // 初始化
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < m; j++) {
            for(char c = 'a'; c <= 'z'; c++) {
                int ptr = j;
                while( ptr && c != t[ptr+1] ) {
                    ptr = next[ptr];
                }
                if( c == t[ptr+1] )  ptr++;
                if( ptr < m )  dp[i][ptr] = (dp[i][ptr] + dp[i-1][j]) % MOD;
            }
        }
    }
    
    int res = 0;
    for(int i = 0; i < m; i++) {
        res = (res + dp[n][i]) % MOD;
    }
    cout << res << endl;
    
    return 0;
}

你可能感兴趣的:(手撕算法,动态规划,算法)