1052. 设计密码
题目描述
你现在需要设计一个密码 S,S 需要满足:
- S 的长度是 N;
- S 只包含小写英文字母;
- S 不包含子串 T;
例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串。
请问共有多少种不同的密码满足要求?
由于答案会非常大,请输出答案模 1 0 9 + 7 10^9+7 109+7 的余数。
输入格式
第一行输入整数 N,表示密码的长度。
第二行输入字符串 T,T 中只包含小写字母。
输出格式
输出一个正整数,表示总方案数模 1 0 9 + 7 10^9+7 109+7 后的结果。
数据范围
1
≤
N
≤
50
1≤N≤50
1≤N≤50
1
≤
∣
T
∣
≤
N
1≤|T|≤N
1≤∣T∣≤N,
∣
T
∣
|T|
∣T∣ 是 T 的长度。
样例
输入样例 1
2
a
输出样例 1
625
输入样例 2
4
cbc
输出样例 2
456924
问题分析
状态定义:dp[i][j]
:密码长度为i
,其后缀与模式串匹配的最大长度为j
的方案总数。
状态转移:借助 KMP 算法的 next 数组实现。若当前密码长度为 i-1,处于状态 j。在当前密码后面加上一个字符c
,使得新的密码长度为i
。密码由长度为i-1
变为i
,需要重新计算当前的所处状态。使用 KMP 算法更新新密码的后缀与模式串匹配的最大长度,记为ptr
。此时dp[i][ptr]
可以由dp[i-1][j]
转移过来,密码长度为i
,处于状态ptr
的总方案数需要加上dp[i-1][j]
。
初始状态:dp[0][0]
,即密码为空,且处于状态 0,即后缀与模式串匹配的最大长度为 0 的方案总数为 1.
第 i 轮的部分状态转移示意图如下,其中节点中的数字j
表示结点的状态,即密码长度为i
时,密码后缀与模式串匹配的最大长度为j
。
最终的结果值:
∑
i
=
0
m
−
1
d
p
[
n
]
[
i
]
\sum_{i=0}^{m-1} dp[n][i]
∑i=0m−1dp[n][i],即对所有密码长度为 n,且状态不处于m
的所有方案总数进行求和。
注意:状态转移过程中,不计算状态 m 的方案数,这样最终的总方案数将不包括 S 包含子串 T 的情况。
复杂度分析
时间复杂度: O ( 26 N 3 ) O(26N^3) O(26N3)
程序代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int MOD = 1e9+7;
int main()
{
int n;
cin >> n;
string t;
cin >> t;
int m = t.size();
// 让子串下标从1开始
t = " " + t;
vector<int> next(m + 1, 0);
// 计算next数组
for(int i = 2, j = 0; i < t.size(); i++) {
while( j && t[i] != t[j+1] ) {
j = next[j];
}
if( t[i] == t[j+1] ) j++;
next[i] = j;
}
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
dp[0][0] = 1; // 初始化
for(int i = 1; i <= n; i++) {
for(int j = 0; j < m; j++) {
for(char c = 'a'; c <= 'z'; c++) {
int ptr = j;
while( ptr && c != t[ptr+1] ) {
ptr = next[ptr];
}
if( c == t[ptr+1] ) ptr++;
if( ptr < m ) dp[i][ptr] = (dp[i][ptr] + dp[i-1][j]) % MOD;
}
}
}
int res = 0;
for(int i = 0; i < m; i++) {
res = (res + dp[n][i]) % MOD;
}
cout << res << endl;
return 0;
}