1052. 设计密码

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

S 的长度是 N;
S 只包含小写英文字母;
S 不包含子串 T;
例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串。

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

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

输入格式

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

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

输出格式

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

数据范围

1≤N≤50,
1≤|T|≤N,|T|是T的长度。

输入样例1:
2
a
输出样例1:
625
输入样例2:
4
cbc
输出样例2:
456924
代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 55, mod = 1e9 + 7;

int n, ne[N], f[N][N];
char s[N];

/*
    KMP + 状态机:
        对于本题来说,要保证设计的密码中不包含子串T,我们可以想到KMP的目的--->快速找到子串出现的起始位置!
        因此对于本题的第三个要求,不能有子串T,即表示KMP算法中只要j走不到模板串的末尾就一定不包含子串T!


    1. 这也是一个状态机问题!对于状态机问题,我们首先要确定状态:
        状态就是:KMP中j来回可以跳到的位置!(即j可以取0 ~ m共m + 1种状态,入口就是0号位置)

    2. 考虑j可以跳的情况:会发现j可以跳的位置是完全由模式串i的位置决定的,i位置对应的字符可以取(a ~ z),因此
        对于i的每种选法,j都有一个唯一跳法使得i和j+1位置对应的字符相等!
    也就是说对于模板串的j对应的m + 1种状态,每种状态都有26种选法(a ~ z),因此:我们就可以建一个具有m + 1个点,
        每个点都有26条边的图!

    3. 我们可以从入口0号位置开始跳,跳到的每一个位置都是一个合法的字符串(注意:不能跳到m位置,跳到m位置就表示包含子串T了)

    4. 也就是说:每一个合法的字符串,都可以一一对应到一种KMP上的状态机的走法。
              反过来说:每一种合法的KMP走法,都一一对应一个合法的字符串!
              即:合法字符串的方案数等价于KMP的状态机走法数!我们只要计算统计KMP的j的走法数即可!

    因此:最终的答案就是从入口开始走,不走到m位置,一共走n步(设计的密码S的长度限制),一共有多少种不同的路线!

        具体来说:对于模式串i的每一种选法方案,都会对应模板串j的一种走法;j的每一种走法都会对应模式串i的一种选法的合法方案!

    --------------------------------------- DP分析 ---------------------------------------------------

    状态表示:f[i][j]表示 “已经” 枚举了第i个字母(或第i步),处于j状态(即KMP中j可以跳到的0 ~ m+1种位置状态)的方案数!
    状态计算:考虑f[i][j]是有哪个状态(记该状态为A)转移过来的(简而言之:就是当前状态的方案数是由各类A的方案数之和组成)
        设j状态是由u状态跳过来的,即
                    f[i][j] = f[i - 1][u]
        解释:当前第i步处于j状态自然是从第i-1步跳过来的,并且是从可以跳到j状态的u状态跳过来的!
        注意:对于第i步,由于i位置26种选法的不同,j状态是由KMP动态移动求得,可能会出现j状态多次指向同一个位置,
              所以,准确来说应该是:f[i][j] += f[i - 1][u]

        由于模式串我们是从0开始的(即枚举的模式串的每一个位置的选法的下标),而对于步数来说是从1开始的:
            因此,准确来说应该是:f[i + 1][j] += f[i][u]

        对应到下方代码:u状态是从j状态跳过来的,因此:f[i + 1][u] += f[i][j]

    分析完毕!
    
    (每一个状态都包含了很多种走法)
 
    最终答案:f[n][j]的和(j可取0,1,2 ... m-1)

*/

int main()
{
    cin >> n >> s + 1;
    int m = strlen(s + 1);

    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++)               // 枚举步数为i + 1(枚举下标为i的字符,i从0开始)
        for (int j = 0; j < m && j <= i; j++) // 枚举j可以跳到的状态(0 ~ m共m种状态,必然不能跳到m)
            for (char k = 'a'; k <= 'z'; k++)
            { // 枚举模式串i可以取的 a-z 26种状态!
                // 对于模式串i的每种选法,利用KMP找到j可以跳到的位置u
                int u = j;
                while (u && k != s[u + 1])
                    u = ne[u];
                if (k == s[u + 1])
                    u++;
                // 只要没有跳到m及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 << endl;
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追寻远方的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值