注意事项:
本题为"KMP字符串匹配"的扩展题,建议先阅读这篇文章并理解。
状态机的基本定义:状态机-百度百科。
题目:
你现在需要设计一个密码 S,S 需要满足:
- S 的长度是 N;
- S 只包含小写英文字母;
- S 不包含子串 T;
例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串。
请问共有多少种不同的密码满足要求?
由于答案会非常大,请输出答案模 10e9+7 的余数。
输入格式
第一行输入整数N,表示密码的长度。
第二行输入字符串T,T中只包含小写字母。
输出格式
输出一个正整数,表示总方案数模 10e9+7 后的结果。
数据范围
1≤N≤50,
1≤|T|≤N,|T|是T的长度。
输入:
2
a
输出:
625
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 55, mod = 1000000007;
int n, m; //密码长度n,子串长度m,
char s[N]; //子串,
int f[N][N], ne[N];
int main() {
//读入
cin >> n >> s+1;
m = strlen(s+1);
//kmp的next数组处理,
for (int i = 2, j = 0; i<=n; i++) {
while (j != 0 && s[i] != s[j+1]) j = ne[j];
if (s[i] == s[j+1]) j++;
ne[i] = j;
}
f[0][0] = 1; //初始化,对于第0位密码,且匹配字串的位置为0时的方案数为1,
for (int i = 0; i<n; i++) { //枚举密码长度,
for (int j = 0; j<m; j++) { //枚举密码长度为i,且子串s匹配的位置为j时的状态,
for (char k = 'a'; k<='z'; k++) { //对于第i+1位字母,枚举26种可能的情况,
//当前密码位数为i,且子串匹配的位置是j,尝试用i+1为k的情况进行跳转,
//1.如果u能够跳转到子串s的最后一位(u==m),说明密码第i+1位为k时,密码中会包含子串s,
//2.反之则说明当密码的i+1位为k时不会包含子串s (u<m),也就是合法方案,可以进行更新,
int u = j;
while (u != 0 && k != s[u+1]) u = ne[u];
if (k == s[u+1]) u++;
if (u < m) {
f[i+1][u] = (f[i+1][u] + f[i][j]) % mod;
}
}
}
}
//求方案总和,这里不取到m是因为当匹配到子串的第m位时,说明当前方案包含了整个子串,显然是不合法的方案,
int res = 0;
for (int i = 0; i<m; i++) res = (res + f[n][i]) % mod;
cout << res;
return 0;
}
思路:
题目中要求有26个小写字母,那么对于密码中的第i
位来说,就有26种不同的选择,而对于每种选择,又都有m
(子串长度)种可跳转到的状态,
(kmp next数组向前跳转 或 当前字母相等就向后跳转一位)。
而且对于不同的子串,跳转状态也不同,比起前两天的点对点的状态机来说,这题明显要复杂的多。
还是经典的y式dp分析法,
1.状态表示
f[i][j]
:
对于第i
位密码,且子串匹配(KMP匹配)的位置为j
时的所有方案,属性为Sum。
2.状态计算
从代码的实际含义出发,当密码的i+1位为k时不包含子串s (u<m),也就是合法方案时,可以进行更新, 直接从f[i][j]
(有i位密码时且子串匹配为第j位)转移过来:
f[i+1][u] += f[i][j]
如果有所帮助请给个免费的赞吧~有人看才是支撑我写下去的动力!
声明:
算法思路来源为y总,详细请见https://www.acwing.com/
本文仅用作学习记录和交流