歌唱王国
题目链接:luogu P4548
题目大意
多次询问,每次给你一个字符串,然后有 n 种字符,猴子随机打字。
每个字符打出来的概率相同,然后打出一个串使得给出串是它的子串就停止,问你停止的时候打出来的字符串的期望长度。
思路
首先简单说一下用生成函数的做法:
\(f_i\) 是长度为 \(i\) 结束的概率,\(g_i\) 是长度为 \(i\) 还没结束的概率。
那一个经典的时候是每个 \(f_i\) 贡献倍率是 \(i\),那我们要的答案其实就是 \(F'(1)\)
然后列相关的式子。
首先接下来打了一个字符,分成结束了和没有结束。
\(xG(i)+1=F(x)+G(x)\)
那考虑如果你一直按要求加,加 \(m\) 次一定能结束,但是可能在之前就结束了,因为有 border。
于是我们设 \(op_i\) 是 \([1,i]\) 是否是这个串的 border:
\(G(x)(\dfrac{1}{n}x)^m=\sum\limits_{i=1}^ma_iF(x)(\dfrac{1}{n}x)^{m-i}\)
然后把第一个式子求导:
\(xG'(i)+G(x)=F'(x)+G'(x)\)
\(F'(x)=(x-1)G'(x)+G(x)\)
那我们要的是 \(F'(1)\),直接带入:\(F'(1)=G(1)\)
那也就是要求 \(G(1)\),那就利用上另一个式子,也带入:
\(G(1)(\dfrac{1}{n})^m=\sum\limits_{i=1}^ma_iF(1)(\dfrac{1}{n})^{m-i}\)
\(G(1)=\sum\limits_{i=1}^ma_iF(1)n^i\)
考虑 \(F(1)\) 是多少,那思考一下就是每一项的系数加起来,那所有长度结束的概率的和不就是 \(1\) 嘛。
\(ans=F'(1)=G(1)=\sum\limits_{i=1}^ma_in^i\)
不难扩展出当每个字符不一样概率的时候,那每个字符概率是 \(p_i\),给出字符串是 \(b_i\),那就是:
\(ans=F'(1)=G(1)=\sum\limits_{i=1}^ma_i(\prod\limits_{j=1}^ip_{b_i})\)
然后说人类智慧。
这是一个有关鞅(martingale)的方法,不过我们可以把它具象成公平赌博中的博弈。
考虑这么一个赌场,每天选择的字符按顺序是你要求的字符,每天会有一个新人进来,一开始他只有一块钱,他也会按你要求的字符按顺序每天赌,会把全部钱都拿来赌,赔率是 \(1:\)这个字符出现概率的倒数,赌输了就走。
然后赌场把字符放完就让所有人滚。
首先赔率显然没问题,因为要公平。
那接着你注意到是公平博弈,所以赌场要不赚不亏。
那我们可以看赚钱的人总共赚了多少。
那就是:
\(w=\sum\limits_{[1,i]is\ border}^ma_i(\prod\limits_{j=1}^ip_{b_i})\)
那赌场要不亏不赚,那进来的钱是多少,是人数,其实也就是字符串长度。
那赌场应该让 \(w\) 个人进来,那就是字符串的期望长度就是 \(w\)。
(在鞅的角度大概是你每个时刻每个赌徒的钱数是一个商,然后所有赌徒钱加起来的值 \(M_i\) 也是鞅)
(然后 \(T\) 是判断要不要停下来的停时 \(\{T=n\}\),然后有个可选停止定理有 \(\mathbb{E}M_T=\mathbb{E}M_0\))
(那一开始 \(M_0=0\),所以 \(\mathbb{E}T=M_T\))
(小孩子不懂事瞎说的)
那实现很简单,直接每次建 KMP,然后跳 fail 即可。
代码
#include<cstdio>
#define mo 10000
using namespace std;
const int N = 1e5 + 100;
int n, t, m, a[N], fail[N], cf[N];
int main() {
scanf("%d %d", &n, &t);
cf[0] = 1; for (int i = 1; i < N; i++) cf[i] = cf[i - 1] * n % mo;
while (t--) {
scanf("%d", &m);
for (int i = 1; i <= m; i++) scanf("%d", &a[i]);
int j = 0;
for (int i = 2; i <= m; i++) {
while (j && a[j + 1] != a[i]) j = fail[j];
if (a[j + 1] == a[i]) j++; fail[i] = j;
}
int ans = 0;
for (int now = m; now; now = fail[now]) (ans += cf[now]) %= mo;
if (ans < 1000) printf("0");
if (ans < 100) printf("0");
if (ans < 10) printf("0");
printf("%d\n", ans);
}
return 0;
}