【洛谷4548】[CTSC2006] 歌唱王国(概率生成函数)
- 有一个长度为\(n\)的文本串和一个初始为空的序列。
- 每次随机产生一个\([1,m]\)的新数加入序列中,求序列中出现给定文本串时的期望长度。
- 数据组数\(\le50\),\(n,m\le10^5\)
概率生成函数
论文题,一个非常诡异的科技,然后这道题是被当作第一道例题拿来讲的。。。
关于概率生成函数可见这篇博客:生成函数学习笔记(三)——概率生成函数初探。
关于\(border\)
应该是字符串问题,尤其是这类掷骰子问题的一个重要概念。
对于一个字符串\(S\),若其长为\(i\)的前缀\(S[1,i]\)和长为\(i\)的后缀\(S[n-i+1,n]\)相同,则称\(S[1,i]\)为\(S\)的一个\(border\)。
具体要求\(border\),只要\(KMP\)或者\(Hash\)即可(个人倾向于后者,简单好懂)。
式子推导
我们设\(f_i\)表示序列最终长度为\(i\)的概率,并定义辅助序列\(g_i\)表示序列长度到达\(i\)不结束的概率,并另设\(a_i\)表示\(S[1,i]\)是否为\(S\)的\(border\)。
通过分析得到下面两个式子:
\[F(x)+G(x)=1+xG(x)\\
G(x)\cdot(\frac1mx)^L=\sum_{i=1}^La_i\cdot F(x)\cdot(\frac1mx)^{L-i}
\]
简单解释一下,对于第一个式子,容易发现\(F(x)+G(x)\)的第\(i\)项是序列长度能达到\(i\)的概率,而\(xG(x)\)的第\(i\)项是序列长度为\(i-1\)时不结束的概率,显然两者等价,而\(1\)用于补足常数项。
对于第二个式子,显然给一个未结束的序列后面强行加上给定序列它一定会结束,但不一定加完才会结束,还有可能已添加的序列末尾是给定序列的一个\(border\),那么直接枚举是哪个\(border\)即可。
然后我们利用这两个式子来推导一下,考虑期望就是\(F'(1)\),因此对第一个式子求导一下:
\[F'(x)+G'(x)=xG'(x)+G(x)
\]
代入\(x=1\)并移项消去\(G'(1)\)得到:
\[F'(1)=G(1)
\]
然后我们就想通过第二个式子求出\(G(1)\),因此把\(x=1\)代入,注意到其中的\(F(1)\)根据概率生成函数的性质它就等于\(1\):
\[G(1)\cdot(\frac1m)^L=\sum_{i=1}^La_i\cdot (\frac1m)^{L-i}\\
G(1)=\sum_{i=1}^La_i\cdot m^i
\]
所以说这题最终就被化成了这样一个简单的式子!!!
代码:\(O(Tn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 10000
using namespace std;
int n,m;
struct Hash
{
#define ull unsigned long long
#define CU Con ull&
ull x,y;I Hash() {x=y=0;}I Hash(CU a) {x=y=a;}I Hash(CU a,CU b):x(a),y(b){}
I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
}h[N+5],pw[N+5],sd(324682339,456789001);
int main()
{
RI Tt,i,x,p,t;for(scanf("%d%d",&m,&Tt),pw[0]=i=1;i<=N;++i) pw[i]=pw[i-1]*sd;W(Tt--)
{
for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),h[i]=h[i-1]+pw[i]*x;//预处理哈希
for(t=0,p=i=1;i<=n;++i) p=1LL*p*m%X,h[i]*pw[n-i]==h[n]-h[n-i]&&(t=(t+p)%X);//枚举每个border计算答案
t<1000&&putchar('0'),t<100&&putchar('0'),t<10&&putchar('0'),printf("%d\n",t);//输出四位,不足补0
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒