题目
Description
输入文件: string.in
输出文件: string.out
给定一个由且仅由字符 'H' , 'T' 构成的字符串 (S) .
给定一个最初为空的字符串 (T) , 每次随机地在 T 的末尾添加 'H' 或者 'T' .
问当 (S) 为 (T) 的后缀时, 在末尾添加字符的期望次数.
Input
输入只有一行, 一个字符串 (S) .
Output
输出只有一行, 一个数表示答案.
为了防止运算越界, 你只用将答案对 (10^9+7) 取模.
题解
题目可以理解成不断向(T)添加字符, 直到(S)匹配上(T)(()(S)为模式串()), 的期望长度。
于是我们可以预处理出失配指针(f_i)
我们可以设(dp_i)表示现在已匹配到(S_i), 在匹配上(S_{i+1})的期望次数。
于是我们可以分类讨论一下。
如果这一次直接匹配上(S_{i+1}), 那么这一部分的贡献为(frac{1}{2} imes 1 = frac{1}{2})
如果这一次匹配失败跳了失配指针, 贡献为匹配上(S_{f_i,;i+1})的期望次数(()参考KMP匹配过程()), 也就是(frac{1}{2}(sum_{k = f_i}^i dp_k) + frac{1}{2}),(()加(frac{1}{2})是因为要匹配上(S_{i+1})())
于是(dp_i = frac{1}{2} + frac{1}{2} + frac{1}{2}(sum_{k = f_i}^i dp_k))
注意到等号两边都出现了(dp_i), 于是我们可以把上面的式子当成一个方程, 解出来是(dp_i = 2 + sum_{k = f_i}^{i-1} dp_k), 我们用前缀和优化一下, 单次转移就变成(O(1))了。
代码
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1000010;
char S[N];
int f[N], nxt[N][2];
int sum[N], dp[N];
int main()
{
scanf("%s", S + 1);
int n = strlen(S + 1);
f[1] = 0;
int j = 0;
for (int i = 2; i <= n; i++)
{
while (j && S[i] != S[j+1]) j = f[j];
if (S[i] == S[j+1]) j++;
f[i] = j;
}
for (int i = 0; i < n; i++)
if (S[i+1] == 'H')
nxt[i][0] = i + 1, nxt[i][1] = nxt[f[i]][1];
else
nxt[i][0] = nxt[f[i]][0], nxt[i][1] = i + 1;
sum[0] = f[0] = 2;
for (int i = 1; i < n; i++)
{
int g;
if (S[i+1] == 'H') g = nxt[i][1]; else g = nxt[i][0];
dp[i] = ((sum[i-1] - (g ? sum[g-1] : 0) + 2) % mod + mod) % mod;
sum[i] = (sum[i-1] + dp[i]) % mod;
}
printf("%d
", (sum[n-1] + mod) % mod);
return 0;
}