zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    已解决[Authentication failed for token submission,Illegal hexadecimal charcter s at index 1]
    远程快速安装redis和远程连接
    远程快速安装mysql
    Swiper的jquery动态渲染不能滑动
    微服务架构攀登之路(三)之gRPC入门
    微服务架构攀登之路(二)之RPC
    微服务架构攀登之路(一)之微服务初识
    Go语言中new和make的区别
    Go语言实战爬虫项目
    Go语言系列(十一)- 日志收集系统架构
  • 原文地址:https://www.cnblogs.com/littlehb/p/15736226.html
Copyright © 2011-2022 走看看