字符串普及题 2
几天前我还会写普及组题,现在已经不会写了
题意
给你一个仅由字符 \(\text{H,T}\) 构成的字符串 \(S\)。
有一个初始为空的字符串 \(T\),每次随机在 \(T\) 的末尾添加 \(\text{H}\) 或 \(\text{T}\)。
问当 \(S\) 为 \(T\) 的后缀时,在末尾添加字符的期望次数(即 \(T\) 的期望长度)。
\(|S|\le 10^6\)
题解
真的是个普及组题
不考虑构建 \(T\) 串,只考虑构建 \(S\) 串。
那么问题相当于转化为:初始时你在 \(s\) 串的第 \(0\) 位,当你在第 \(i\) 位时,有 \(0.5\) 的概率走到第 \(i+1\) 位,另外 \(0.5\) 的概率走到第 \(fail[i+1]\) 位。求走到第 \(|S|\) 位的期望步数。
\(fail[i]\) 表示将第 \(i\) 位字符反转后,\(S\) 串自己对自己做 KMP 时第 \(i\) 位的 \(fail\) 指针指向的位置。两次 KMP 预处理即可。
然后这就是个普及组的期望 \(dp\)……
方法 1
设 \(f[i]\) 表示第一次从第 \(i\) 位走到第 \(i+1\) 位的期望步数。
此时转移为 \(f[i] = 0.5\times 1 + 0.5\times (1+f[fail[i+1]]+f[fail[i+1]+1]+...+f[i-1]+f[i])\)
后面那一长串的意思就是 到达上一个匹配位置,再逐步走过来。
由于是递推,前缀和优化即可。
方法 2
另外一种方法:
设 \(f[i]\) 表示第 \(i\) 位走到第 \(n\) 位的期望步数。
转移为 \(f[i] = 0.5\times f[i+1] + 0.5\times f[fail[i+1]] + 1\)
移项得 \(f[i+1] = 2\times f[i] - f[fail[i+1]] - 2\)
好像是带环的解方程,不能直接倒推 \(f\)。
但不难发现,这种倒推在起点处的性质很好,即 \(f[0] = 0.5\times f[1] + 0.5\times f[0] + 1\),即 \(f[1]=f[0]-2\)。
由于对于任意的 \(i\ge 2\),\(f[i]\) 最多只与 \(2\) 个满足 \(j\lt i\) 的 \(f[j]\) 有关,所以从 \(f[0]\) 和 \(f[1]\) 出发可以递推出其它所有点的相关信息。
下面考虑什么是“相关信息”。
我们知道终点处 \(f[|S|]=0\)。如果能得到一个关于 \(f[0]\) 和 \(f[|S|]\) 的等式就好了,这样我们可以直接算出 \(f[0]\)。
于是把每个位置用 \(a\times f[0]+b\) 表示出来。正着扫一遍 \(S\) 串递推出来所有位置的系数 \(a\) 和 \(b\),得到 \(f[n]\) 的两个系数后就可以得到关于 \(f[0]\) 和 \(f[|S|]\) 的等式了。
关于第二种做法,后来我想到一个问题:最后一步算 \(f[0]\) 要做除法(除以 \(a[n]\)),不会得小数么?
然后把所有位置的 \(a\) 都输出出来,发现全是 \(1\)……
下意识地看了下转移式,突然明白了些什么……
所以这题可以不记系数 \(a\)……
本质就是解方程高斯消元
#include<bits/stdc++.h>
#define N 1000010
#define mod 1000000007
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x; return 0-x;
}
char s[N];
int n,fail[N],_fail[N],g[N];
void KMP(){
int j=0;
for(int i=2; i<=n; ++i){
while(j && s[i]!=s[j+1]) j=fail[j];
if(s[i]==s[j+1]) ++j;
fail[i]=j;
}
j=0;
for(int i=2; i<=n; ++i){
while(j && s[i]==s[j+1]) j=fail[j];
if(s[i]!=s[j+1]) ++j;
_fail[i]=j, j=fail[i];
}
}
int main(){
scanf("%s",s+1); n=strlen(s+1);
KMP();
g[1]=mod-2;
for(int i=2; i<=n; ++i) g[i] = ((g[i-1]*2%mod - g[_fail[i]] - 2) % mod + mod) % mod;
cout<<mod-g[n]<<endl;
return 0;
}