字符串普及题 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;
}

转载于:https://www.cnblogs.com/scx2015noip-as-php/p/190813c.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值