考前复习一下期望相关知识,这题的期望还是很巧妙的。
设 (f_{i}) 表示已经买到了 (i) 张不同的邮票的期望步数,(g_{i}) 表示表示已经买到了 (i) 张不同的邮票的期望花费;(h_{i}) 表示已经买到了 (i) 张不同的邮票、想买下剩下邮票的期望步数,(y_{i}) 表示表示已经买到了 (i) 张不同的邮票、想买下剩下邮票的期望花费。
1. 分析性质直接推
我们可以得到一些好玩的式子们,有一些是对的,有一些是错的:
这个式子是对的,为什么呢?我抽到新邮票的概率是 (frac{n-i+1}{n}),那么抽到新邮票的期望次数就是 (frac{n}{n-i+1})。上述式子甚至可以写成 $ f_n = sumlimits { frac{1}{i} } $。
这个式子显然是错的。为什么呢?有两种解释方法,一是每种 (f_n) 的取值的贡献不是一样的,不满足线性性;也可以看做是 (mathbb{E}(x^2)) 作为不独立的两个变量相乘,不具有线性性。
这个式子又是对的了,(我也不知道为什么它是对的,目前 lzy 大佬正在证明它)。这个成立就能得到一些很好玩的式子,比如 $ f_{n} = sumlimits_{i=1}^{n}(frac{n}{i} sumlimits_{j=1}^{i}(frac{n}{i})) $。
2. 用无穷级数直接表示最终答案
众所周知 (mathbb{E}(x) = sumlimits x_iP_i),于是我们可以直接表示出最终的答案:
其中 (frac{i+i^2}{2}) 是贡献,右边的是概率;概率等于合法情况数除以总情况数,会发现合法情况数就是一个不定方程正整数解的个数的形式,总情况数简单,于是就有了上述式子。
但是这个式子其实没有什么用,一个是精度无法接受,一个是要计算到极高的项才能保证精度,(n=10^4) 时大概在 (4 imes 10^8) 这样。
更误:这个东西似乎不能等价于不定方程,因为 (n) 个物品是两两不同的,所以应该用第二类斯特林数乘以 ((n-1)!) (乘以阶乘是因为集合也有序),得到
3. 方程法解决递归型期望
这个来源于分类讨论,两种情况分别讨论一下,移项一下就能得到递推式。
和上面的方程的来源是一样的,但是需要用到费用提前计算——(h_i+1) 的含义是替后面的提前加上。但这个式子还是有一些难以解释通的地方,为什么递归下去后每次提前计算的贡献是相同的?那不是成了平行四边形吗?这个方法使得期望满足了线性性——竖着我统计没有线性性,横过来看就能巧妙地去掉乘积项,就拥有了线性性。
更为不严谨的方程: 设买了 (x) 次邮票,答案就是 (frac{x+x^2}{2})。设 (w_i) 表示已经买到了 (i) 种邮票、要买剩下的的邮票的次数平方的期望(注意和期望的平方的区别),那么可以得到
于是答案就是 (frac{h_0 + w_0}{2})。但是——这么做是有问题的——因为默认了平方的期望等于期望的平方!但是,他居然能过!错的做法能过,说明过的做法都是错的,细思极恐啊!
4. 一个极尽严谨的方法
注意到上面所有东西不严谨的地方在于在变量不独立的情况下使用了乘积的期望等于期望的乘积,可能只是这题它恰好是对的,但不见得下一回它会对。那我们就把乘法的步骤替换成无穷级数求和就行了。
考虑我要买到第 (i) 种邮票,若这次我花了 (1) 步,那期望的花费是 (f_{i-1} + 1);如果花了 (i) 步,期望花费是 (f_{i-1} + 1 + f_{i-1} + 2) 元......(期望在加法上满足线性性,不管它独不独立。),(k) 步的期望花费是 (kf_{i-1} + frac{k+k^2}{2})(这个地方可以运用乘法是因为这个是取值的乘积,而不是期望的乘积),走 (k) 步的概率是 ((frac{i-1}{n})^{k-1} imes frac{n-i+1}{n}),于是得到贡献函数 (d(i)=g(i)-g(i-1)) 满足
我相信它化简出来的结果恰好等于 (frac{n}{n-i+1}f_{i}),但是这里篇幅有限,留给读者,证明又繁又难,读者自证又烦又南。
关于随机数
const int maxn = 1e5 + 1e2;
bool vis[maxn]; int n = 31;
unsigned int sd = 233;
inline unsigned int rd()
{
return sd ^= sd >> 13, sd ^= sd << 17, sd ^= sd >> 5;
}
int main()
{
int cnt = 0;
long double tot = 0;
while (true)
{
cnt++;
for (int i = 1; i <= n; i++) vis[i] = 0;
int tmp = 0, t = 0;
while (tmp < n)
{
tot += ++t;
int now = rd() % n + 1;
tmp += !vis[now]; vis[now] = true;
}
if (cnt % 1000000 == 0)
{
cout << cnt << " " << tot / cnt << endl;
}
}
return 0;
}
这是一份用于随机检验的代码,但是会发现收敛后依然跟答案相差很大——事实证明,这是个检验随机数性能的好方法,我们上面代码中的 rd 函数就比 C++ STL::rand 来得与数学解接近的多得多。