【洛谷 P4548 [CTSC2006]歌唱王国】【概率生成函数+KMP】

该博客介绍了如何利用概率生成函数和KMP算法解决一个概率问题:给定序列A,随机序列B以等概率添加1到m的整数,求B期望长度。通过建立概率生成函数的方程组,并对KMP算法的应用进行分析,得出O(n)时间复杂度的解决方案。
摘要由CSDN通过智能技术生成

题意

给一个长度为 n n n 的序列 A A A。有一个空序列 B B B,每次会等概率随机往该序列的末尾加入 1 1 1 m m m 中的一个整数,若 A A A 成为了 B B B 的子串,则停止。求序列 B B B 的期望长度。
n ≤ 1 0 5 n\le 10^5 n105

分析

定义一个离散随机变量 X X X 的概率生成函数为 F ( z ) = ∑ i ≥ 0 P ( X = i ) z i F(z)=\sum_{i\ge 0}P(X = i)z^i F(z)=i0P(X=i)zi

其中 P ( X = i ) P(X=i) P(X=i) 表示 X X X 取值为 i i i 的概率。不难得到 F ( 1 ) = 1 , F ′ ( 1 ) = E ( X ) F(1)=1,F'(1)=E(X) F(1)=1,F(1)=E(X)

回到题目,设 a i a_i ai 表示 A A A 长度为 i i i 的前缀是否是 A A A 的一个 border,是则为 1 1 1,否则为 0 0 0。设 f i f_i fi 表示结束时 B B B 的长度恰好为 i i i 的概率,其概率生成函数为 F ( x ) F(x) F(x) g i g_i gi 表示 B B B 的长度为 i i i 时还没结束的概率,生成函数为 G ( x ) G(x) G(x)

则有等式 F ( x ) + G ( x ) = 1 + x G ( x ) (1) F(x)+G(x)=1+xG(x)\tag{1} F(x)+G(x)=1+xG(x)(1)

G ( x ) ( 1 m x ) n = ∑ i = 1 n a i F ( x ) ( 1 m x ) n − i (2) G(x)(\frac{1}{m}x)^n=\sum_{i=1}^na_iF(x)(\frac{1}{m}x)^{n-i}\tag{2} G(x)(m1x)n=i=1naiF(x)(m1x)ni(2)

( 1 ) (1) (1) 式可以理解为,在序列末尾随机加入一个数字后,有可能结束,也有可能不结束。

( 2 ) (2) (2) 式可以理解为,在一个还未结束的串后面加入原串,则一定会结束。但可能还没全部加入就已经结束。右边的 i i i 枚举的是结束后还没被加入部分的长度,显然这部分一定是原串的 border。

( 1 ) (1) (1) 式求导并代入 x = 1 x=1 x=1,可得 F ′ ( x ) + G ′ ( x ) = G ( x ) + x G ′ ( x ) F'(x)+G'(x)=G(x)+xG'(x) F(x)+G(x)=G(x)+xG(x)

F ′ ( 1 ) = G ( 1 ) F'(1)=G(1) F(1)=G(1)

( 2 ) (2) (2) 式代入 x = 1 x=1 x=1 可得 G ( 1 ) = ∑ i = 1 n a i m i F ( 1 ) = ∑ i = 1 n a i m i G(1)=\sum_{i=1}^na_im^iF(1)=\sum_{i=1}^na_im^i G(1)=i=1naimiF(1)=i=1naimi

用 KMP 求出 a i a_i ai 即可。时间复杂度 O ( n ) O(n) O(n)

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 100005;
const int mo = 1e4;

int n, t, a[N], nx[N], m;
bool vis[N];

void get_next(int n)
{
	nx[0] = -1; nx[1] = 0;
	int i = 2, j = 0;
	while (i <= n)
		if (j == -1 || a[j + 1] == a[i])
		{
			nx[i] = ++j;
			i++;
		}
		else j = nx[j];
}

void pri(int x)
{
	vector<int> vec;
	while (x) vec.push_back(x % 10), x /= 10;
	while (vec.size() < 4) vec.push_back(0);
	for (int i = 3; i >= 0; i--) putchar(vec[i] + '0');
	puts("");
}

int main()
{
	scanf("%d%d", &n, &t);
	while (t--)
	{
		scanf("%d", &m);
		for (int i = 1; i <= m; i++) scanf("%d", &a[i]), vis[i] = 0;
		get_next(m);
		int ans = 0, k = m;
		while (k) vis[k] = 1, k = nx[k];
		for (int i = 1, w = n % mo; i <= m; i++, w = w * n % mo) (ans += vis[i] * w) %= mo;
		pri(ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值