【luogu P4548】歌唱王国(期望)(生成函数 / 思维)(KMP)

发布时间 2023-03-22 21:13:05作者: あおいSakura

歌唱王国

题目链接: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;
}