【探究向/总集篇】用命分析概率型生成函数(PGF)
皎月半洒花
2020-05-05 17:28:22
大概是最近几天的科研成果qwq。
「学不会的生成函数」+「学不会的概率论」= `?`
算是比较详细地研究了一下这部分内容吧。个人认为是全网能找到最详细的指南了 qwq。
然后因为听说谷民不是很喜欢 `\mathscr`,于是就给 replace 成了 `\mathbf`。
~~虽然我还是喜欢`\mathscr`~~ 。
# 概率型生成函数
虽然概率型生成函数本质上就是普通型生成函数,只不过多了一个对应关系。
即,如果对于某个离散型随机变量 $X\in\mathbb Z$ ,存在数列 $\{a_n\}$ 满足 $\Pr(X=i)=a_i$ ,那么离散型随机变量 $X$ 的 **概率型生成函数$(\mathbf{PGF})$** 为
$$
\mathbf{F}(z)=\sum_{i=0}^{\infty} \Pr(X=i) z^i
$$
那么首先有
$$
\mathbf{F}(1)=\sum_{i=0}^{\infty}[z^i]\mathbf{F}(z)=1
$$
同时根据期望的定义
$$
E(X)=\sum_{i=0}^{\infty}i\Pr(X=i)
$$
可以知道期望就是 $\mathbf{F}$ 的一阶导数系数和,即
$$
E(X)=\sum_{i=0}^{\infty}[z^i]\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(z)
$$
那么同时根据方差的定义以及期望的线性性可以得到:
$$
\mathsf{Var}(X)=E\left((X-E(X))^{2}\right)=E\left(X^{2}-2\cdot X \cdot E(X)+E(X)^{2}\right)=E\left(X^{2}\right)-E(X)^{2}
$$
从而有
$$
\mathsf{Var}(X)=\sum_{i=0}^{\infty}[z^i]\left(\dfrac{\mathrm{d^2}}{\mathrm{d} z^2}\mathbf{F}(z)+\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(z)-\left(\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(z)\right)^2\right)
$$
是因为
$$
E(X^2)=\sum_{i=0}^{\infty}i^2\cdot \Pr(X=i)=\sum_{i=0}^{\infty}i\cdot (i-1)\cdot \Pr(X=i)+\sum_{i=0}^{\infty}i\cdot \Pr(X=i)
$$
可以知道前面一项是二阶导,后面一项是一阶导。
然后…然后就可以做题了(倒)。
# [2013 Multi-University Training Contest 5] Dice
> 一个 $m$ 面的公平骰子,求:
>
> 1、最后 $n$ 次结果相同就结束的期望次数。
>
> 2、求最后 $n$ 次结果全不同就结束的期望次数。
>
> 保证 $n,m \leq 10^6$,且对第二问 $n \leq m$ 。
## 朴素做法
即用 dp 来做。
### 第一问
设 $f_i$ 表示最后 $i$ 次相同,期望还要多少次结束。那么 $f_n=0$ ,求的就是 $f_0$ 。那么可以知道有转移
$$
f_{i}=\dfrac{1}{m}f_{i+1}+\frac{m-1}{m}f_1+1
$$
发现并不好直接做,考虑差分得到
$$
f_{i}-f_{i+1}=(m-1)f_i-(m-1)f_{1}-m=m(\frac{m-1}{m}f_i-\frac{m-1}{m}f_1-1)=m(f_{i-1}-f_i)
$$
并且由 $f_0=f_1+1$ 可以知道 $f_n-f_{n+1}=m^n$,于是最后答案就是 $1+\sum_{i=1}^{n-1}m^i$ 。
### 第二问
设 $g_i$ 表示最后 $i$ 次均不相同,期望还要多少次结束。那么 $g_n=0$,求 $g_0$ 。考虑
$$
g_i=\frac{m-i}{m} g_{i+1}+\frac{1}{m}\cdot \boldsymbol{\sum_{j=1}^i g_j}+1
$$
(注意加粗的部分,自己一开始因为不细心推挂了…)
那么还是差分
$$
\begin{aligned}
g_{i-1}-g_i&=-\frac{i-1}{m}g_i+\frac{1}{m}\cdot \boldsymbol{\sum_{j=1}^{i-1} g_j}+1\\
&=-\frac{(i-1)(m-i)}{m^2}g_{i+1}-\left(\frac{i-1}{m^2}-\frac{m}{m^2}\right)\cdot\sum _{j=1}^ig_j-\frac{m}{m^2}g_i-\frac{i-1}{m}+1\\
&=-\frac{(i-1)(m-i)}{m^2}g_{i+1}-\left(\frac{i-1}{m^2}-\frac{m}{m^2}\right)\cdot\sum _{j=1}^ig_j-\frac{m}{m^2}\left(\frac{m-i}{m} g_{i+1}+\frac{1}{m}\cdot \sum_{j=1}^i g_j+1\right)-\frac{i-1}{m}+1\\
&=-\frac{(i-1)(m-i)+(m-i)}{m^2}g_{i+1}+\left(\frac{m-i+1-1}{m^2}\right)\cdot\sum _{j=1}^ig_j+\frac{m-(i-1)-1}{m}\\
&=-\frac{i\cdot (m-i)}{m^2}g_{i+1}+\frac{m-i}{m^2}\cdot\sum _{j=1}^ig_j+\frac{m-i}{m}\\
&=\left(\frac{m-i}{m}\right)\left(-\frac{i}{m}g_{i+1}+\frac{1}{m}\cdot \sum_{j=1}^{i} g_j+1\right)\\
&=\frac{m-i}{m}(g_{i}-g_{i+1})
\end{aligned}
$$
~~不会告诉你中途推岔匹了好几次~~
然后就类似上面那个 case 了,也是可以线性做的。
## PGF 做法
### 第一问
即考虑套路设 PGF,设 $f_i$ 表示恰好在扔第 $i$ 次结束时的概率,$g_i$ 表示扔了第 $i$ 次仍没结束的概率。考虑对这两个东西建立 PGF,分别为 $\mathbf{F,G}$, 那么有方程:
$$
\begin{aligned}\mathbf F(z)+\mathbf G(z)&=z\cdot \mathbf G(z)+1 \qquad(1)\\\mathbf G(z)\cdot \left(\frac{1}{m}z\right)^n&=\sum_{i=1}^n \mathbf{F}(z)\cdot \left(\frac{1}{m}z\right)^{n-i} \qquad(2)\end{aligned}
$$
第一个方程可以看做是废话,就是多扔了一次之后,要么结束要么不结束,$\mathbf G$ 右移一位可以看作是 $g_{i-1}$ 。
第二个方程的意思是在现在的串后面接一个合法,也就是 $n$ 位都相同的串,那么可以知道等式左边的含义是「一定可以结束」;等式右边则是考虑可能会存在没有加满 $n$ 个就结束的情况。个人的理解是,为了保证等式两边是在讨论相同的情况,所以仍然需要把后面的 $n-i$ 次操作算进来,可以知道这样是不影响前面恰好取完了的那些情况。于是根据这个东西依旧可以解出来和做法一相同的结果来。
### 第二问
定义依旧不变。考虑方程大概是
$$
\mathbf F(z)+\mathbf G(z)=z\cdot \mathbf G(z)+1\qquad(1)
$$
和
$$
\mathbf{G}(z)\cdot \left(\frac{1}{m}z\right)^n\cdot \dfrac{m!}{(n-m)!}=\sum_{i=1}^n \mathbf{F}(z)\cdot \left(\frac{1}{m}z\right)^{n-i} \cdot \dfrac{(m-i)!}{(m-i-(n-i))!}\qquad(1)
$$
然后就暴力解就好了。
# [CTSC2006] 歌唱王国
> 简化版题面:
>
> 给定一个长为 $n$ 的由 $1\sim m$ 组成的序列 $A$,同时每次掷一颗均匀的 $m$ 面骰子,求期望掷多少次骰子才可以使得序列中存在一个连续的子串和 $A$ 相同。
>
> $1\leq n,m\leq 10^6$ 。
考虑和上题差不多的 PGF 做法:
$$
\begin{aligned}
\mathbf F(z)+\mathbf G(z)&=z\cdot \mathbf G(z)+1 \\\mathbf G(z)\cdot \left(\frac{1}{m}z\right)^n&=\sum_{i=1}^n \mathbf{F}(z)\cdot \left(\frac{1}{m}z\right)^{n-i}\cdot \mathbf{\zeta}(i)
\end{aligned}
$$
其中 $\zeta(i)=\texttt{「 A[1...i] 是否是 A[1...n] 的 Border」}$ 。 还是比较显然的,因为如果是拼到第 $i$ 位就可以停止,那么 $A[1...i]$ 必然是个 $\sf border$ 。
最后可以直接推出来
$$
\dfrac{\mathrm d}{\mathrm{d}z}\mathbf{F}'(1)=\sum_{i=1}^n \zeta(i)\cdot m^{i}
$$
# [SDOI2017] 硬币游戏
> 给定 $n$ 个长为 $m$ 的由 $0/1$ 组成的序列 $A_i$,同时每次掷一颗均匀的双面骰子,求期望掷多少次骰子才可以使得序列中存在一个连续的子串在 $A_1\sim A_n$ 中出现。
>
> $n,m\leq 300$.
考虑串与串之间可以来回匹配,于是对每个串都定义一个 $\mathbf{F}_i(z)$ 表示首次出现的是第 $i$ 个串,且当前随机长度为 $j$ 的概率,然后单独定义一个 $\mathbf{G}(z)$ 表示游戏结束,那么有
$$
\sum_{i=1}^n\mathbf{F}_i(z)+\mathbf{G}(z)=\mathbf{G}(z)\cdot z+1
$$
同时定义 $match(i,j)_k=[~A_i[1...k]=A_{j}[k...m]~]$ ,那么有对于每个 $i$ 的一组方程枚举在最后拼上一个什么东西:
$$
\mathbf{G}(z)\cdot \Pr(A_i[1...n])\cdot x^m=\sum_{j=1}^n \mathbf{F}_j(z)\sum_{k=1}^m match(i,j)_k\cdot \Pr(A_i[k+1...m])\cdot x^{m-k}
$$
不难知道意思是 $A_j$ 和 $A_i$ 可以放在一起匹配,枚举 $i$ 某个同时是 $A_j$ 后缀的前缀进行配对。
那么考虑要求的就是 $\mathbf{F}_1(1),\mathbf{F}_2(1),\mathbf{F}_3(1),\cdots$ 。发现可以由方程 $(2)$ 得到 $n$ 个关系,同时因为并没有要求期望所以 $(1)$ 式没有任何用处。但是可以发现,根据概率的规范性可以得到
$$
\sum \mathbf{F}_i(1)=1
$$
于是就有 $n+1$ 组关系,同时有包含 $\mathbf{G}(1)$ 在内的 $n+1$ 个未知元,就可以愉快地高消了。
# [ZJOI2013] 抛硬币
> 有一枚硬币,抛出正面 `H` 的概率为 $\frac{a}{b}$,抛出反面 `T` 的概率为 $1-\frac{a}{b}$。现在 T 小朋友开始玩丢硬币的游戏,并且把每次抛出的结果记录下来,正面记为 `H`,反面记为 `T`,于是她得到了一个抛硬币序列 `HTHHT`…。
>
> 她突然想到一个问题:在抛出正面和反面概率都是 $\frac{1}{2}$ 的情况下,要使得抛出的序列出现只包含 `H` 和 `T` 目标序列,期望要抛多少次。
>
> 这么简单的题目她当然是一眼秒。于是她在解决了这个弱化很多的问题以后,开始思考对于一般情况下,抛出正反面的概率不一定相同,且抛出的目标序列不一定为 `HT` 时需要的期望步数。然而经过很长一段时间的苦思冥想仍然无果,于是她开始求助于你。
>
> 简化版题面:
>
> > 给出一个两面的均匀骰子,正面和反面的概率分别是 $\frac{a}{b}$ 和 $1-\frac{a}{b}$ 。并给出一个长度为 $n$ 的 $01$ 序列 $A$ 。
> >
> > 同时有一个一开始为空的序列。每次掷骰子,如果是反面,就在当前序列末尾写一个 $1$ ,否则写一个 $0$ ,如果发现此时序列中恰好有一个连续子串是 $A$ 则停止。求期望多少次才会停止操作。
>
>
> 我认为合理的数据范围:$1\leq n\leq 10^6$,概率对 $998244353$ 取模。
>
> ZJOI2013 的数据范围:$1\leq n\leq 10^3$ 输出确切概率并保留**既约分数形式** 。
全网似乎没人用 PGF 做,这就很爽。有种中了头彩的感觉…但是其实写这题的时候我是十分崩溃的…
从 5.3 晚上 8:30 左右一直写,写到 10:00 PM 左右发现有地方写错了,但是由于要回宿舍了所以被迫终止。第二天早上来了又开始写,写着写着发现还要写一波高精 gcd 和高精除,于是就把早上的课给翘了。写完了发现慢的一匹,只能有 $50pts$,然后决定去学一学压位。发现压位也不难,然后决定压 $4$ 位;写着写着又发现压 $8$ 位也不是不可以,然后改来改去改成了 $90pts$ ,发现最大的点比时限慢一倍…于是就去扒自己的 FFT 板子,拼拼凑凑之后发现由于可能会爆精度所以不能压 8 位,只能压 $4$ 位,结果更慢了。然后决定改成 NTT,结果发现 NTT 要对神必数取模导致压两位可能都有问题,然后就自闭了,决定放弃多项式科技去优化自己的压位高精。发现有些地方似乎合并同类项之后会很快,原来乘 $O(n)$ 次改完只需要乘 $O(1)$ 次,然后左改右改终于卡过了洛谷上的1s时限…
期间一度怀疑自己算法的正确性,但是想了想也没什么更靠谱的做法了,只不过这个写法是 $L^2\log V$ 的,$L^2$ 大概也就是 $10^6$ 的范围,只是这个 $\log V$ …他确实有点毒瘤。因为最大可以到 $V=10^{16000}$ 左右,所以高精度除法二分起来就十分爆炸…大概极限是 $5\cdot 10^9$ 的运算量。不过…好在最后是过了,虽然有点卡时。
算了一下似乎 FFT 的复杂度更对一点,$L\log L\log V$ 大概是 $10^7\sim 10^8$ 左右的运算量。但是自己的 FFT 水平实在太差…于是就还是多项式乘法了。可能什么时候心情好就会补一下这个锅?
以下是正文,就直接把发在谷上的题解糊上来了:
_____
考虑设 $f_i$ 表示扔了 $i$ 次骰子之后恰好停止的概率,$g_i$ 表示扔了 $i$ 次骰子之后仍未结束的概率。同时考虑对这两个东西建立 PGF,分别为 $\mathbf{F,G}$, 那么有方程:
$$
\mathbf F(z)+\mathbf G(z)=z\cdot \mathbf G(z)+1 \qquad(1)
$$
其意义是,扔了一次之后,要么结束要么不结束,$\mathbf G$ 右移一位可以看作是 $g_{i-1}$ 。同时还有
$$
\mathbf G(z)\cdot \Pr(A[1...n])=\sum_{i=1}^n \mathbf{F}(z)\cdot \Pr(A[i+1...n])\cdot \zeta(i) \qquad(2)
$$
其意义是,考虑在现在的串后面接一个可以让这个过程直接结束的串,也就是接一个 $A$,$\Pr(s[1...n])$ 表示串 $s$ 出现的概率,那么可以知道等式左边的含义是「一定可以结束」;等式右边则是考虑可能会存在没有加满 $n$ 个、只加了前 $i$ 个字符就结束的情况,多乘一个 $\Pr(A[i+1...n])$ 本身是没有意义的,只是为了构造出等式,可以理解为两边都钦定扔了 $n$ 次——发现这种情况要是想要出现,就必须满足 $A[1...i]$ 是 $A$ 的一个 $\sf border$ ,所以 $\zeta(i)=[A[1...i]\in\mathbf{Border}]$ .
之后考虑如何消元。首先对 $(1)$ 式求导可以得到
$$
\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(z)+\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{G}(z)=\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{G}(z)\cdot z+\mathbf{G}(z)\cdot 1
$$
也就是可以知道
$$
\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(z)=\mathbf{G}(z)
$$
同时对于 $(2)$ 式,将 $z=1$ 代入可以得到
$$
\mathbf{G}(1)\cdot \Pr(A[1...n]) =\sum_{i=1}^n\mathbf{F}(1)\cdot \zeta(i)\cdot \Pr(A[i+1...n])
$$
因为 $\mathbf{F}(1)=1$ ,所以可以得到
$$
\mathbf{G}(1)=\dfrac{\sum_{i=1}^n \zeta(i)\cdot \Pr(A[i+1...n])}{\Pr(A[1...n])}
$$
然后根据
$$
E(X)=\dfrac{\mathrm{d}}{\mathrm{d} z}\mathbf{F}(1)
$$
发现这题就做完了。复杂度线性。
但是注意本题要求以既约分数的形式保留精确值。因为我好久好久没写过高精度了,于是就想借此机会封装一个模板。然后…然后就写了快 $7h+$。注意到由于要求既约分数,所以要写高精度gcd,写法可以借鉴 `[SDOI2009]Super GCD` ,同时还要写高精除高精,个人没有找到什么好方法,于是就写的二分,复杂度大概是 $L^2\log V$ 的样子。
然后复杂度似乎是 $n\cdot L^2\log V$ ,并不可以过,于是考虑剪枝+卡常:
0、…高精度压位是必要的吧?这边我选择压 $8$ 位,因为发现 $10^3\cdot 10^{16}$ 恰好卡到了 `long long` 的上界。
1、发现二分时左右边界可以缩短很多,即 $l,r$ 都至多和 $V / \gcd$ 的长度相差 $1$ ,所以可以用这个来确定边界。亲测可以快大概 $6$ 倍左右(但是还是 T,极限数据大概要跑 $4s+$) 。
2、发现计算答案时,展开后存在很多公因式。于是可以提取公因式之后再计算。亲测可以快 $4$ 倍左右。
然后…大概就过了。中间写了很久的原因在于,我本来想尝试 FFT,后来发现自己没有封装好的 FFT…囧…写了半天发现自己 FFT 的常数还不如压位快…然后就没有然后了。
__________
以上都是无聊的套路题,还是下面的题比较有趣
# [趣题]一个有趣的概率小问题 · 改
题目来源是[这里](http://roosephu.github.io/2017/12/31/condexp/),与本题略有出入:
> 一个 $n$ 面的骰子,每一面标号 $1$ 到 $n$ 。有个初始为 $0$ 的计数器,每次扔骰子,按顺序执行以下过程:
>
> 1、扔出了奇数:那么计数器清零。
>
> 2、扔出了偶数:计数器加 $1$ 。
>
> 3、扔出了 $n$:游戏结束。
>
> 问结束时计数器上显示的数值的期望。
>
> 保证 $n$ 是偶数。
考虑如果按照套路设 $f_i$ 表示扔到 $i$ 结束的概率,$g_i$ 表示扔到 $i$ 没有结束的概率,会存在问题。因为根据题设,会重复到达某个权值 $v$ 很多很多次,所以设概率是不妥的。
考虑 PGF 的一个翻版,对着期望建立生成函数(你可以叫他 EGF【雾】,虽然本质上就是对 PGF 求了一个一阶导数):设 $f_i$ 表示扔到 $i$ 结束的**期望次数**,$g_i$ 表示扔到 $i$ 没有结束的**期望次数**,对这两个东西建立普通型生成函数可以得到
$$
\mathbf{F}(z)+\mathbf{G}(z)=\left(\mathbf{G}(z)\cdot \dfrac{z}{2}+1\right)+\frac{1}{2}\cdot \mathbf{G}(1)
$$
其中左边的意思当然是,要么结束要么不结束,换个意思就是「到达 $i$ 的期望总次数」,右边第一项是有 $\dfrac{1}{2}$ 的概率从 $z$ 转移过来,有 $\dfrac{1}{2}$ 的概率到达 $0$,那么此时有等式「原来到达所有 $i$ 的期望次数」=「现在这一步到达 $0$ 的期望次数」。
同时也会有
$$
\mathbf{F}(z)=\frac{x}{n}\cdot \mathbf{G}(z)
$$
即恰好扔到 $n$ 的转移,根据题设应该先 $+1$ ,再结束。
感觉还是比前面的题有趣的?
# [来源保密的比赛 1A] Probability
> 有一个随机变量 $z$, 初始 $z=0$.
>
> 执行 $n$ 次操作: 每次操作是从 $0$ 到 $k$ 中以 $p_t$ 的概率选择一个整数 $t$,并令 $z:=z+t$ 。
>
> 求 $\min(z,x)$ 的期望. 答案模 $998244353$.
>
> $k\leq 100,n\leq 10^9,x\leq \min\{10^7,\frac{5\times 10^7}{k}\}$ 。
设选择整数的概率型生成函数为 $P(y)=\sum_{i=0}^kp_i$ ,那么取了 $n$ 次之后的概率型生成函数就是 $Q(y)=P^n(y)$ 。
那么不难知道答案为
$$
\sum_{i=0}^{n\times k} [y^i]Q\cdot \min(x,i)=\sum_{i=0}^{x-1} [y^i]Q\cdot i + \left(1-\sum_{i=0}^{x-1} [y^i]Q\right)\cdot x
$$
呃…虽然没学过 PGF 到底怎么化,但是概率上求 $z$ 补集就是 $1-z$ 还是比较 xxs 的结论吧…
然后现在问题就集中在怎么求 $P^n(y)$ 的前 $k$ 项了。发现可以暴力多项式快速幂,以获得 $40\sim 60$ 左右的成绩。
然后是神奇的多项式技巧…大概是考虑对于 $P^{n+1}(y)$ 求导有两种方式:
$$
\left(P^{n+1}(y)\right)^{\prime}=(n+1) P^{n}(y) P^{\prime}(y)
$$
$$
\left(P^{n+1}(y)\right)^{\prime}=\left(P^{n}(y)\right)^{\prime} P(y)+P^{n}(y) P^{\prime}(y)
$$
第一个就是链式法则,第二个则是拆出一个 $P(y)$ 来再用求导的乘法运算法则。
考虑联立之后
$$
nP^n(y)(P(y))'=(P^n(y))'P(y)
$$
然后考虑现在的问题是,已知了 $P^n(y)$ 的前几项系数,求出后面的系数。考虑一个这样的思路:每次先求出 $(P^n(y))'$ 的第 $d$ 项,然后积分出 $P^n(y)$ 的第 $d+1$ 项。发现每个 $[y^d]P^n(y)$ 只会对 $y^d\sim y^{d+k}$ 这些产生贡献,所以考虑枚举到一个 $d$ 的时候向后刷表;考虑这样求出的是 $(P^n(y))'P(y)$ ,直接模拟多项式除法即可得到 $(P^n(y))'$ ,这样做也是单次 $O(k)$ 的。
对于多项式除法这部分,可以考虑对于每个 $[y^d] (P^n(y))'P(y)$ 都是这么计算得到的:
$$
\sum_{p=0}^k[y^p]P(y)\times [y^{d-p}](P^n(y))'
$$
然后就减去所有的 $p>0$ 的那些结果,最后乘上一个 $[x^0]P(y)$ 的逆元即可。
顺便复习一下线性求逆元:
考虑模数是 $m$,那么设 $m=p\cdot x+r$,可以得到:
$$
\begin{aligned}
p\cdot x+r&\equiv 0\pmod m\\
p\cdot r ^{-1}+x^{-1}&\equiv 0 \pmod m\\
x^{-1}&\equiv -\lfloor\frac{m}{x}\rfloor\cdot (m\bmod x)^{-1}\pmod m
\end{aligned}
$$
# [来源保密的比赛 ?] Barrel
![](Barrel.png)
![](Barrel2.png)
![](Barrel3.png)
![](Parrel4.png)
读题让人十分迷惑…注意这题里面 $c_{i,j}$ 是给定的,只不过因为样例里面恰好是没有这部分输入而已……
_____
光是读题就会让人十分迷惑的题…
因为与符号冲突了,于是决定把题面中的 $z$ 改成 $c$ ,$z$ 维持原来的形式幂级数定义不变。
考虑深刻地挖掘题目性质:对于每个桶,所有年份体积的酒的体积总和是 $1$ 。所以如果设第 $i$ 个桶里年份为 $j$ 的酒体积为 $V_{i,j}$ ,可以发现本题要求
> 第 $n$ 个桶内取一微元酒的单价期望/方差,
实际上就是在求
> **第 $n$ 个桶有 $V_{n,j}$ 的概率变成第 $j$ 年的酒,求第 $n$ 桶酒的期望价值。**
考虑先算这个概率。
考虑一个突破点,发现每次给出去的一定是均匀的,所以不需要考虑给出去的如何分配。换言之如果这一回合给进来的总体积(不看年份)是 $V$ ,那么给出去的必然是 $1-V$ 并且是均匀的。
根据上一点,理应想到,$1$ 号酒桶就是突破口,因为 $1$ 号桶进来的只会是 $f$ 。于是考虑设每个桶内每一轮新倒入的体积为 $m_i$,那么会有
$$
m_i=\begin{cases}f&\mathrm{if}~(i=1)\\\sum_{j=1}^{i-1}c_{i,j}&\mathrm{otherwise}\end{cases}
$$
于是考虑设 $f_{i,j}$ 表示第 $i$ 个桶内变成第 $j$ 年酒的概率,对每一个 $i$ 建立概率型生成函数 $\mathbf F_i(z)$ ,那么会有
$$
\mathbf F_{i}(z)=\mathbf F_{i}(z)\cdot z\cdot (1-m_i)+\sum_{j=1}^i m_j\mathbf{F}_j(z)
$$
这个式子本质上模拟了取酒、倒酒这个过程。
考虑计算答案,根据上文可以知道
$$
\mathsf{Var}(X)=E(X^2)-E^2(X)
$$
考虑后面一项本质上是
$$
\left(\sum_{i=0}^{+\infty} i\cdot c^i\cdot [z^i]\mathbf{F}_{n}(z)\right)^2
$$
让后发现…如果将不定元 $z$ 赋值成 $c$ ,那么上式就是
$$
\left(c\cdot \frac{\mathrm{d}}{\mathrm{d}z}\mathbf{F}_n(c)\right)^2
$$
同理第一项就是
$$
\sum_{i=0}^{+\infty} i\cdot c^{2}\cdot [z^{2\cdot i}]\mathbf{F}_{n}(z^2)
$$
等价于
$$
c^4\cdot \frac{\mathrm{d^2}}{\mathrm{d}z^2}\mathbf{F}_n(c^2)+c^2\cdot \frac{\mathrm{d}}{\mathrm{d}z}\mathbf{F}_n(c^2)
$$
其中后半部分为了补全系数。
然后就可以直接做了,用一些求导技巧维护
$$
\mathbf{F}_{i}(z), \mathbf{F}_{i}^{\prime}(z), \mathbf{F}_{i}\left(z^{2}\right), \mathbf{F}_{i}^{\prime}\left(z^{2}\right), \mathbf{F}_{i}^{\prime \prime}\left(z^{2}\right)
$$
的值即可。复杂度 $n^2$ 。
# 代码合集
大概是上面的题写了代码就会丢到这里一份,看心情保留完整版还是局部。
```c++
//hdu4652 Dice
//author: Orchidany
int q ;
db ans ;
db res ;
int k, m, n ;
db expow(db x, int y){
db ret = 1.0 ;
while (y){
if (y & 1)
ret = 1.0 * ret * x ;
x = 1.0 * x * x ; y >>= 1 ;
}
return ret ;
}
int main(){
while (cin >> q){
while (q --){
cin >> k >> m >> n ;
if (!k){
if (m <= 1) ans = 1.0 * n ;
else ans = (expow(m, n) - 1.0) / (1.0 * (m - 1.0)) ;
}
else {
res = 1 ; ans = 0.0 ;
for (int i = 1 ; i <= n ; ++ i)
res *= 1.0 * m / (m - i + 1), ans += res ;
}
printf("%.10lf\n", ans) ;
}
}
```
```cpp
//CTSC2006 歌唱王国
//author: Orchidany
int ans ;
int f[N] ;
int base[N] ;
int T, n, m ;
int expow(int x, int y){
int ret = 1 ;
while (y){
if (y & 1)
ret = 1ll * ret * x % P ;
x = 1ll * x * x % P ; y >>= 1 ;
}
return ret ;
}
int main(){
cin >> n >> T ;
while (T --){
cin >> m ; int j = 0 ; ans = 0 ;
for (int i = 1 ; i <= m ; ++ i)
base[i] = qr(), f[i] = 0 ; f[1] = 0 ;
for (int i = 2 ; i <= m ; ++ i){
while (j && base[j + 1] != base[i]) j = f[j] ;
if (base[j + 1] == base[i]) ++ j ; f[i] = j ;
}
while (m) add(ans, expow(n, m), P), m = f[m] ;
if (ans < 10) printf("000") ;
else if (ans < 100) printf("00") ;
else if (ans < 1000) printf("0") ; cout << ans << '\n' ;
}
}
```
```cpp
//ZJOI2013 抛硬币
//author : Orchidany
const ll Base = 100000000 ;
int get_L(int x){
int ret = 0 ;
if (!x) return 1 ;
while (x) ret ++, x /= 10 ;
return ret ;
}
struct Big_Num{
ll v[2051] ;
bool mk ; int len, lent ;
void reset(){
len = 0, mk = 0, memset(v, 0, sizeof(v)) ;
}
void out_put(){
if (mk && !(len <= 1 && !v[1])) putchar('-') ;
for (int i = len ; i >= 1 ; -- i){
if (i == len){
printf("%lld", v[i]) ;
continue ;
}
for (int j = 1 ; j <= 8 - get_L(v[i]) ; ++ j) putchar('0') ;
printf("%lld", v[i]) ;
}
//puts("") ;
}
template <typename T>
void set_v(T x){
if (x < 0) mk = 1, x = -x ;
while (x) v[++ len] = x % Base, x /= Base ;
lent = (len - 1) * 8 + get_L(x) ;
}
void set_v(char *x, int L){
int p = 0 ; reset() ;
if (x[1] == '-') ++ p, mk = 1 ;
len = (L - p) / 8 + (((L - p) % 8) > 0) ;
for (int i = L, k = len ; i >= p + 1 ; i -= 8, -- k){
for (int j = min(i - p, 8) ; j >= 1 ; -- j)
v[k] = v[k] * 10ll + (x[i - j + 1] - '0') ;
}
reverse(v + 1, v + len + 1) ; lent = L - p ;
}
Big_Num friend operator ~ (Big_Num A){
A.mk ^= 1 ; return A ;
}
bool friend operator ^ (const Big_Num & A, const Big_Num & B){
if (A.len != B.len) return 0 ;
for (int i = 1 ; i <= A.len ; ++ i)
if (A.v[i] != B.v[i]) return 0 ; return 1 ;
}
bool friend operator < (const Big_Num & A, const Big_Num & B) ;
bool friend operator > (const Big_Num & A, const Big_Num & B) ;
Big_Num friend operator + (const Big_Num & A, const Big_Num & B) ;
Big_Num friend operator - (const Big_Num & A, const Big_Num & B) ;
il bool friend operator < (const Big_Num & A, const Big_Num & B){
if (A.len < B.len) return 1 ;
else if (A.len > B.len) return 0 ;
for (int i = A.len ; i >= 1 ; -- i)
if (A.v[i] < B.v[i]) return 1 ;
else if (A.v[i] > B.v[i]) return 0 ; return 0 ;
}
il bool friend operator > (const Big_Num & A, const Big_Num & B){
if (A.len < B.len) return 0 ;
else if (A.len > B.len) return 1 ;
for (int i = A.len ; i >= 1 ; -- i)
if (A.v[i] > B.v[i]) return 1 ;
else if (A.v[i] < B.v[i]) return 0 ; return 0 ;
}
il bool friend operator <= (const Big_Num & A, const Big_Num & B){
if (A.len < B.len) return 1 ;
else if (A.len > B.len) return 0 ;
for (int i = A.len ; i >= 1 ; -- i)
if (A.v[i] < B.v[i]) return 1 ;
else if (A.v[i] > B.v[i]) return 0 ; return 1 ;
}
il bool friend operator >= (const Big_Num & A, const Big_Num & B){
if (A.len < B.len) return 0 ;
else if (A.len > B.len) return 1 ;
for (int i = A.len ; i >= 1 ; -- i)
if (A.v[i] > B.v[i]) return 1 ;
else if (A.v[i] < B.v[i]) return 0 ; return 1 ;
}
il Big_Num friend operator - (const Big_Num & A, const Big_Num & B){
Big_Num res ; res.reset() ;
Big_Num p, q, t ; p = A, q = B ;
if (p.lent < q.lent) { return (~ (q - p)) ; }
for (rg int i = 1 ; i <= p.len ; ++ i) res.v[i] = p.v[i] - q.v[i] ;
for (rg int i = 1 ; i <= p.len ; ++ i)
if (res.v[i] < 0) res.v[i + 1] --, res.v[i] += Base ;
res.len = p.len ; res.v[0] = -1 ;
for (rg int i = res.len + 20 ; i >= 0 ; -- i)
if (res.v[i]) { res.len = i ; break ; }
if (!res.len) res.len ++ ;
if (res.v[res.len] < 0)
res.mk = 1, res.len --, res.v[res.len] = Base - res.v[res.len] ;
res.lent = (res.len - 1) * 8 + get_L(res.v[res.len]) ;
return res ;
}
il Big_Num friend operator + (const Big_Num & A, const Big_Num & B){
Big_Num res ;
res.reset() ; Big_Num p, q ;
if (A.len > B.len)
p = A, q = B ; else p = B, q = A ;
if (p.mk && q.mk) res.mk = 1 ;
else if (p.mk && !q.mk) { p.mk = 0 ; return (q - p) ; }
else if (!p.mk && q.mk) { q.mk = 0 ; return(p - q) ; }
for (rg int i = 1 ; i <= p.len ; ++ i) res.v[i] = p.v[i] + q.v[i] ;
res.len = p.len ;
for (rg int i = 1 ; i <= p.len + 1 ; ++ i)
if (res.v[i] >= Base)
res.v[i + 1] += res.v[i] / Base, res.v[i] %= Base ;
for (rg int i = res.len + 10 ; i >= 0 ; -- i)
if (res.v[i]) { res.len = i ; break ; }
res.lent = (res.len - 1) * 8 + get_L(res.v[res.len]) ;
return res ;
}
il Big_Num friend operator * (const Big_Num & A, const Big_Num & B){
Big_Num res ; res.reset() ; res.len = A.len + B.len ;
for (rg int i = 1 ; i <= A.len ; ++ i)
for (rg int j = 1 ; j <= B.len ; ++ j)
res.v[i + j - 1] += A.v[i] * B.v[j] ;
for (rg int i = 1 ; i <= res.len + 10 ; ++ i)
res.v[i + 1] += res.v[i] / Base, res.v[i] %= Base ;
res.v[0] = -1 ;
for (rg int i = res.len + 10 ; i >= 0 ; -- i)
if (res.v[i]) { res.len = i ; break ; }
if (!res.len) res.len ++ ;
res.lent = (res.len - 1) * 8 + get_L(res.v[res.len]) ; return res ;
}
il void div2() {
for (rg int i = len ; i >= 1 ; -- i)
v[i - 1] += (v[i] % 2ll) * Base, v[i] >>= 1 ;
v[0] >>= 1 ; while(v[len] == 0) len -- ;
lent = (len - 1) * 8 + get_L(v[len]) ;
}
il void mul2() {
int r = 0 ;
for (rg int i = 1 ; i <= len ; ++ i) {
v[i] = v[i] * 2 + r, r = 0 ;
if(v[i] >= Base) {
r = v[i] / Base, v[i] %= Base ;
if (i == len) len ++ ;
}
}
lent = (len - 1) * 8 + get_L(v[len]) ;
}
Big_Num friend gcd(Big_Num A, Big_Num B){
int cnt2 = 0 ;
while(!(A ^ B)){
if (A < B) swap(A, B) ;
bool a = A.v[1] & 1, b = B.v[1] & 1 ;
if (!a && !b) A.div2(), B.div2(), ++ cnt2 ;
else if (!b) B.div2() ; else if (!a) A.div2() ; else A = A - B ;
}
Big_Num tmp, pmt ;
tmp.set_v(2), pmt.set_v(1) ;
while (cnt2){
if (cnt2 & 1)
pmt = pmt * tmp ;
tmp = tmp * tmp ; cnt2 >>= 1 ;
}
return (A = A * pmt) ;
}
} ;
Big_Num g, t ;
Big_Num l, r, mid, ans ;
struct Frac{
Big_Num fz, fm ;
il void reset(){
fz.reset() ;
fm.reset() ;
}
il void reverse(){
Big_Num t = fz ; fz = fm, fm = t ;
}
il void div(){
g = gcd(fz, fm) ; r.reset() ;
int lnr = fz.len - g.len ; l.reset() ; l.len = lnr ;
l.v[l.len] = 1 ; r.len = lnr + 1 ; r.v[r.len] = 90000000 ;
while (l <= r){
mid = (l + r) ; mid.div2() ;
if (mid * g >= fz) ans = mid, r = mid - t ; else l = mid + t ;
}
fz = ans ; l.reset() ;
lnr = fm.len - g.len ; l.reset() ; l.len = lnr ;
l.v[l.len] = 1 ; r.len = lnr + 1 ; r.v[r.len] = 90000000 ;
while (l <= r){
mid = (l + r) ; mid.div2() ;
if (mid * g >= fm) ans = mid, r = mid - t ; else l = mid + t ;
}
fm = ans ;
}
il void out_put(){
fz.out_put() ;
putchar('/') ;
fm.out_put() ; puts("") ;
}
template <typename T>
il void set_v(T son, T mum){
fz.set_v(son), fm.set_v(mum) ;
}
template <typename T>
il void set_v(char *son, int Lson, char *mum, int Lmum){
fz.set_v(son, Lson) ; fm.set_v(mum, Lmum) ;
}
il Frac friend operator + (Frac A, Frac B){
Frac res ; res.reset() ;
if (!A.fz.len && !A.fm.len) return B ;
if (!B.fz.len && !B.fm.len) return A ;
if (!(A.fm ^ B.fm)){
Big_Num p = A.fm, q = B.fm, o, s, t ;
o = p * q ; s = q * A.fz ; t = p * B.fz ;
A.fm = o ; B.fm = o ; A.fz = s ; B.fz = t ;
}
res.fm = A.fm ; res.fz = A.fz + B.fz ; return res ;
}
il Frac friend operator - (Frac A, Frac B){
Frac res ; res.reset() ;
if (!(A.fm ^ B.fm)){
Big_Num p = A.fm, q = B.fm, o, s, t ;
o = p * q ; s = q * A.fz ; t = p * B.fz ;
A.fm = o ; B.fm = o ; A.fz = s ; B.fz = t ;
}
res.fm = A.fm ; res.fz = A.fz - B.fz ; return res ;
}
il Frac friend operator * (const Frac & A, const Frac & B){
Frac res ; res.reset() ;
res.fm = A.fm * B.fm ;
res.fz = A.fz * B.fz ;
return res ;
}
il Frac friend operator ^ (const Frac & A, const Frac & B){
Frac res ; res.reset() ;
res.fz = A.fz * B.fz ; res.fm = A.fm ; return res ;
}
il Frac friend operator & (const Frac & A, const Frac & B){
Frac res ; res.reset() ;
res.fm = A.fm * B.fm ; res.fz = A.fz ; return res ;
}
} r1, r2, r3, r0 ;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int f[N] ;
int A, B ;
Frac res ;
char S[N] ;
Frac Rp[N] ;
Frac Pr[N] ;
int base[N] ;
int T, n, m ;
int main(){
cin >> A >> B ;
cin >> (S + 1) ;
r0.set_v(1, B) ;
r1.set_v(A, 1) ;
Rp[0].set_v(1, 1) ;
r2.set_v(B - A, 1) ;
m = strlen(S + 1), Pr[m].set_v(1, 1) ;
for (rg int i = 1 ; i <= m ; ++ i)
base[i] = (bool)(S[i] == 'H') ;
for (rg int j = m ; j >= 1 ; -- j)
Pr[j - 1] = Pr[j] ^ (base[j] ? r1 : r2) ;
for (rg int j = 1 ; j <= m ; ++ j) Rp[j] = Rp[j - 1] & r0 ;
for (rg int i = 2, j = 0 ; i <= m ; ++ i){
while (j && base[j + 1] != base[i]) j = f[j] ;
if (base[j + 1] == base[i]) ++ j ; f[i] = j ;
}
t.reset() ; t.set_v(1) ;
r3 = Pr[0] * Rp[m] ; r3.reverse() ; int k = m ;
while (m) res = res + Pr[m] * Rp[k - m] , m = f[m] ;
res = res * r3 ; res.div() ; res.out_put() ; return 0 ;
}
```
```cpp
//[来源保密的比赛 1A] Probability
//author : Orchidany
int sum ;
int res ;
int ans ;
int base ;
int F[M] ;
int f[M] ;
int G[N] ;
int g[N] ;
int tmp[N] ;
int inv[N] ;
int n, k, x ;
int expow(int a, int b){
int ret = 1 ;
while (b){
if (b & 1)
ret = (ll)ret * a % P ;
a = (ll)a * a % P ; b >>= 1 ;
}
return ret ;
}
int main(){
cin >> n >> k >> x ; inv[1] = 1 ;
for (int i = 0 ; i <= k ; ++ i)
F[i] = qr(), add(sum, 1ll * F[i]) ;
sum = expow(sum, P - 2) ;
for (int i = 0 ; i <= k ; ++ i)
F[i] = 1ll * F[i] * sum % P ;
for (int i = 0 ; i <= k ; ++ i)
f[i] = 1ll * n * F[i + 1] % P * (i + 1) % P ;
for (int i = 2 ; i <= x ; ++ i)
inv[i] = (-1ll * inv[P % i] * (P / i) % P) + P;
base = expow(F[0], P - 2) ; G[0] = expow(F[0], n) ;
for (int i = 0 ; i < x ; ++ i){
for (int j = 0 ; i + j < x && j <= k ; ++ j)
add(tmp[i + j], 1ll * f[j] * G[i] % P) ;
for (int j = 1 ; i - j >= 0 && j <= k ; ++ j)
dec(tmp[i], 1ll * g[i - j] * F[j] % P) ;
g[i] = 1ll * base * tmp[i] % P ;
G[i + 1] = 1ll * g[i] * inv[i + 1] % P ;
add(ans, 1ll * i * G[i] % P) ; dec(res, G[i]) ;
}
res += 1 ; add(ans, 1ll * res * x % P) ; cout << ans << '\n' ; return 0 ;
}
```
剩下的…就先鸽着吧,感觉除了复习高消之外也没啥好写的了。
# 后记
这文章真是莫名其妙的写了好几天,可能是题目钛毒瘤了导致经常出现思维掉线的局面…
其中比较多题目的都是yml论文里的,自己学习了一下,顺便加上了一些自己的心得。
这可能就是…执念吧。