AcWing1052提高课dp系列状态机模型

这篇博客探讨了一种利用状态机和KMP算法来设计满足特定条件的密码的方法。题目要求生成一个长度为N的密码S,其中S不包含特定的子串T。通过建立f[i][j]的状态转移矩阵,表示已生成i位密码且第i位在模式串T中匹配到的位置为j的方案数。利用KMP算法找到下一个字符在T中的匹配位置,并进行状态转移。博客详细解释了状态转移的过程,并提供了相应的C++代码实现。最后,通过累加f[n][0~len-1]得到满足条件的密码方案总数,并对结果取模10^9 + 7。该方法展示了字符串处理和算法在解决复杂问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

状态机 + kmp

你现在需要设计一个密码 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

设f[i][j]表示当前已经生成密码的第i位,且第i位在模式串t中能匹配到的位置为j的方案数
在这里插入图片描述
这表示从s[i - j + 1]到 s[i] 与t[1] 到 t[j] 的这一段两个字符串相等,假设t的长度为len
那么当j==len时,生成的密码不合法,所以在状态转移的过程中,j不能转移到len

那么如何描述状态的转移呢
假设第i + 1位能匹配到在t中的位置为u,通过kmp算法,我们可以找到这个位置u
那么f[i][j]能转移到f[i + 1][u],所以从f[i][j]这一条边到f[i + 1][u]这条路径存在,
那么f[i + 1][u] += f[i][j]
在这里插入图片描述

在图中假设的是s[i + 1] == ‘a’的情况,在实际运行中我们需要考虑s[i + 1]是所有小写字母的情况
所以需要枚举s[i + 1]从’a’到’z’
最后的答案从定义上可知是f[n][0~len - 1]之和

#include <iostream>
#include <cstring>
using namespace std;

const int N = 55, mod = 1e9 + 7;
int f[N][N];
char t[N];
int ne[N];
int n;

int main(){
    cin >> n;
    cin >> t + 1;
    int len = strlen(t + 1);
    
    // kmp模板
    for(int i = 2, j = 0; i <= n; i ++){
        while(j && t[i] != t[j + 1]) j = ne[j];
        if(t[i] == t[j + 1]) j ++;
        ne[i] = j;
    }
    f[0][0] = 1;//当前密码生成0位,在t中匹配位置为0,方案为一个(初始化)
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < len; j ++){
            for(int k = 'a'; k <= 'z'; k ++){//枚举第i + 1位为'a'到'z'的情况
                int u = j;
                while(u && t[u + 1] != k) u = ne[u];
                if(k == t[u + 1]) u ++;//找到第i + 1位在t中所能匹配到的位置
                
                if(u < len) f[i + 1][u] = (f[i + 1][u] + f[i][j]) % mod;
                //答案需要对1e9+7取模
            }
        }
    }
    int res = 0;
    for(int i = 0; i < len; i ++) res = (res + f[n][i]) % mod;
    cout << res << endl;
    return 0;
    
}

感谢指正

### 关于 AcWing 算法基础中的 Python 实现动态规划背包问题模板 以下是基于多重背包问题的朴素版本实现的一个通用 DP 背包问题代码模板。此模板适用于处理物品数量有限制的情况,即每个物品最多可以选择 `s[i]` 次。 #### 代码模板 ```python # 定义最大容量和物品数 N = 110 # 物品数量的最大值加一 f = [0] * N # 初始化状态转移方程数组 def multiple_knapsack(n, m, v, w, s): """ :param n: 总共的物品数目 :param m: 背包总容量 :param v: 列表,存储第i件物品的价值 :param w: 列表,存储第i件物品的重量 :param s: 列表,存储第i件物品的数量上限 :return: 返回能够获得的最大价值 """ for i in range(1, n + 1): # 遍历每一个物品 for j in range(m, 0, -1): # 倒序遍历背包剩余空间 k = 0 while k * v[i] <= j and k <= s[i]: # 添加约束条件k<=s[i] f[j] = max(f[j], f[j - k * v[i]] + k * w[i]) k += 1 return f[m] if __name__ == "__main__": n, m = map(int, input().split()) # 输入物品总数和背包容量 v, w, s = [0] * (n + 1), [0] * (n + 1), [0] * (n + 1) # 存储v,w,s for i in range(1, n + 1): v[i], w[i], s[i] = map(int, input().split()) result = multiple_knapsack(n, m, v, w, s) print(result) ``` 上述代码实现了多重背包问题的核心逻辑[^1]。通过倒序更新的方式避免重复计算,并引入了一个额外变量 `k` 来控制当前物品的选择次数不超过其允许的最大值 `s[i]`。 --- #### 区间动态规划扩展说明 如果遇到更复杂的场景,比如需要记录最优解路径或者涉及区间划分的问题,则可以参考加分二叉树的做法[^3]。此时通常会增加辅助数据结构用于保存中间结果或决策过程。 例如,在某些情况下可能需要用到二维数组来表示子区间的最佳得分情况: ```python dp = [[0] * N for _ in range(N)] # dp[l][r] 表示从l到r这个范围内的最大收益 addition_info = [[None] * N for _ in range(N)] # 记录分割点或其他附加信息 ``` 这种设计模式特别适合那些不仅关注最终答案还关心具体构成细节的应用场合。 --- #### 更高效的优化方法探讨 对于大规模输入的数据集而言,朴素版的时间复杂度较高(O(n*m*sum(s)))。因此实际应用时常采用单调队列等高级技巧进一步降低运行时间开销[^2]。 然而初学者建议先掌握基本形式再逐步深入学习改进策略。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值