题目传送门

题目题解

一、思路

  • 动态规划
    AcWing 1052. 设计密码
    AcWing 1052. 设计密码
    首先怎么确定状态,这里把 \(f[i][j\)] 表示为对于现在生成的密码已经(完成、结束的意思)到了第 \(i\) 个了,并且匹配到当前在子串中的位置是\(j\) 的密码个数。

一个状态机问题,先要明确有几种状态,对于每一个固定的\(i\)\(j\) 来说,有\(m+1\)个状态。
每个状态根据\(s[i]\)的不同,出去的边也不同,我们需要逐个讨论可能的\(a\sim z\)进行研究。

如何判断某一种方案是合法解的呢?联系\(KMP\)的子串匹配方法,就是判断对于固定的 \(i\)\(j\) 判断当前字符是不是和子串中 \(j+1\) 的字符匹配,匹配就\(j++\),不匹配 \(j\) 就回跳。

根据上面的分析,我们再来看这个状态的定义,想一想状态方程,因为每一个字母都对于固定的 \(i\)\(j\) 都有固定的判断结果,那么我们只要对于每一种 \(i\)\(j\)枚举一下\(26\)个字母 ,根据上面的判断方法判断一下是否可能 如果可能就可以状态转移了,其实就是讨论一下某个状态转化到哪个状态。

接下来,是我写题解的惯例,写一下代码每一步的含义和其中一些细节

1:\(KMP\)的预处理,初始化\(next\)数组,代码中用\(ne\)表示
2:循环:
2.1:第一层循环:枚举一下\(i\)的位置,也就是当前已完成密码的长度,不能到\(n\),最长是\(n-1\)(代码中是从\(0\)开始的)
2.2:第二层循环:枚举一下\(j\) 的位置,也就是在子串中的位置
2.3:第三层循环:枚举一下\(a \sim z\) 所有的字母 ,并且利用\(KMP\)判断是否当前密码有子串,如果没有更新\(f[i+1][j]\),为什么是\(i+1\),而不是\(i\) 呢?因为这里定义的状态是已经有的长度不包括当前枚举的字母,我当时也迷惑了一会,现在写出来帮助一下不懂的小伙伴

3:最后把所有可能的 \(j\) 的位置加起来,就是答案,因为\(i\)最后肯定是 \(n\) ,所以枚举一下未知的 \(j\)
\(KMP\)中还有一个细节,就是要用\(u\)来对每一种状态更新,不要用\(j\),不要搞错了,\(j\)是枚举的状态,如果用 \(j\) 的话更新的状态就不对了,注意一下哈。

  • 问题
    为什么这样的状态表示是可行的呢?
    因为\(S\)数组中的第\(n\)位有\(26\)个小写字母,匹配在\(T\)中的位置一定存在(因为不匹配,匹配到的位置是\(0\)),所以把所有\(f[n][0 \sim m-1]\)加起来即为总方案数。

二、原始版本

#include <bits/stdc++.h>

using namespace std;
const int N = 55;
const int mod = 1e9 + 7;

int n;          //n个长度的密码串
int m;          //模板串的长度
int ne[N];      //kmp的ne数组
char p[N];      //模板串
int f[N][N];    //f[i][j]表示密码已经生成了i位,并且第i位匹配到模板串中位置为j时的方案数,这是方案数互相依赖相加的准确依据

int main() {
    //构建的密码长度n
    cin >> n >> (p + 1);//模板串p,模板串的下标是从1开始的
    //计算模板串s的字符串长度
    m = strlen(p + 1);

    //kmp求ne数组,模板代码
    for (int i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }

    //已经匹配了0位,且匹配的子串的位置是0时的方案数为1;(初始化)
    f[0][0] = 1;

    for (int i = 0; i < n; i++)//枚举密码串的每一位,这是一个DP打表的过程,所以从小到大遍历每一位
        for (int j = 0; j < m; j++)//根据状态定义,需要枚举的第二维就是模板串的每一个位置
            //j表示第i位密码匹配到的位置,因为不能包含子串,所以不能匹配到m这个位置
            //认为前面i,j已经完成匹配的情况下,讨论密码串第i+1位的26种可能
            for (char k = 'a'; k <= 'z'; k++) {
                //在s[i+1]=k的时候,模板串需要跳到哪个位置?
                int u = j;
                while (u && k != p[u + 1]) u = ne[u];
                if (k == p[u + 1]) u++;
                //[i+1,u]这个状态是可以被[i,j]转化而来的
                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;
    //将所有的方案数加起来即为总方案数
    printf("%d", res);
    return 0;
}

优化一维复杂度降低到\(n^2\)
将建图部分抽取出来即可。。。!
时间复杂度:\(O(26*n^2)\)

三、预处理优化版本

#include <bits/stdc++.h>

using namespace std;

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

int n;          //n个长度的密码串
int m;          //模板串的长度
int ne[N];      //kmp的ne数组
char p[N];      //模板串
int f[N][N];    //f[i][j]表示密码已经生成了i位,并且第i位匹配到模板串中位置为j时的方案数,这是方案数互相依赖相加的准确依据
int g[N][26];   //


int main() {
    //构建的密码长度n
    cin >> n >> (p + 1);//模板串p,模板串的下标是从1开始的
    //计算模板串s的字符串长度
    m = strlen(p + 1);

    //kmp求ne数组,模板代码
    for (int i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }

    // 预处理(可以优化一下,有重复的不用重新计算)
    for (int j = 0; j < m; j++)
        for (int k = 'a'; k <= 'z'; k++) {
            int u = j;
            while (u && p[u + 1] != k) u = ne[u];
            if (p[u + 1] == k) u++;
            //记录下这个数据
            g[j][k - 'a'] = u;
        }
    // 状态计算
    f[0][0] = 1;
    for (int i = 0; i < n; i++)//枚举密码串的每一位,这是一个DP打表的过程,所以从小到大遍历每一位
        for (int j = 0; j <= m; j++)//根据状态定义,需要枚举的第二维就是模板串的每一个位置
            //j表示第i位密码匹配到的位置,因为不能包含子串,所以不能匹配到m这个位置
            //认为前面i,j已经完成匹配的情况下,讨论密码串第i+1位的26种可能
            for (char k = 'a'; k <= 'z'; k++) {
                //模板串跳到哪个位置?
                //模板串跳到哪个位置,与两个因素有关,1:j现在所在的位置,2:遇到的s[i+1]是什么,即k
                int u = g[j][k - 'a'];
                //状态转移
                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;
    //将所有的方案数加起来即为总方案数
    printf("%d", res);
    return 0;
}

相关文章:

  • 2021-06-18
  • 2021-07-17
  • 2021-10-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-12
猜你喜欢
  • 2022-12-23
  • 2021-12-18
  • 2021-10-14
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案