[BZOJ1426]收集邮票 题解

期望 DP

学习自:Miracle ,非常强大

Statement

\(n\) 种邮票,每种有无数张,收集到的概率都是 \(1/n\) ,第 \(i\) 次收集代价 \(i\) ,问收集所有类型邮票的期望代价

\(n\le 10^4\)

fzoi.top

Solution

naive:粗暴地想,我只需要求出 \(f[i]\) 表示已经有了 \(i\) 类牌还需要的期望次数,然后暴力乘一个等差数列就可以了

具体的,\(f[i]=\dfrac {n-i}{n}(f[i+1]+1)+\dfrac in(f[i]+1)\)

然后设 \(g[i]\) 表示有了 \(i\) 类牌然后需要的期望花费 \(g[i]=\dfrac{n-i}{n}(g[i+1]+calc(f[i+1]+1))+\dfrac in(g[i]+calc(f[i]+1))\) ,其中 \(calc(x)\) 表示等差数列求和,\(=(x^2+x)/2\) ,即直接认为当前是第 \(x\) 次收集

但这样是不对的,因为 \(f[i]\) 表示是期望次数,期望次数和 期望次数的等差数列求和并不能简单类比

也就是说,\(f[i]=E(s[i])\)\(s[i]\) 是一个随机变量表示有 \(i\) 类还需的步数,而等差数列的期望实际上是 \(E((s^2+s)/2)=[E(s^2)+E(s)]/2\)

注意平方的期望和期望的平方的不同,容易理解为什么不能直接等差数列算贡献

于是引入一个类似 费用提前计算 的思想,直接认为在求解 \(g[i]\) 的时候是第一次收集,有这样一个式子

\[g[i]=\dfrac{n-i}n(g[i+1]+f[i+1]+1)+\dfrac in(g[i]+f[i]+1) \]

(你不就是把 calc 去掉了吗

也就是说,直接认为这 \(i\) 类都是白嫖的,然后算贡献。

但由于不是白嫖的,所以,在计算 \(g[i]\) 的时候,需要把 \(g[i+1]\) 的贡献补上。\(g[i+1]\) 期望买了 \(f[i+1]\) 次,而我们之前认为这 \(f[i+1]\) 次都是白嫖的,所以现在全部补上 \(1\)

(所以其实应该叫做费用推迟计算

为什么现在就是对的呢,因为现在需要算的额外贡献变成了 ”有多少个” 需要补钱,是一次方的

边界条件:\(f[n]=g[n]=0\),所以这样就可以做了


那我就是想要算等差数列,我就是喜欢等差数列,就真的没有办法了吗

看到上面那个式子:\(E((s^2+s)/2)=[E(s^2)+E(s)]/2\)

\(E(s)\) 就是 \(f\) ,我们现在只需要算 \(E(s^2)\)

\(h[i]\) 表示从 \(i\)\(n\) 步数平方的期望,有

\[h[i]=\dfrac{n-i}nE((s[i+1]+1)^2)+\dfrac in E((s[i]+1)^2)\\ = \dfrac {n-i}n(h[i+1]+2\times f[i+1]+1)+\dfrac in (h[i]+2\times f[i]+1) \]

哈哈,递推就好了

Code

费用推迟计算:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}
double f[N],g[N],n;

signed main(){
    scanf("%lf",&n);
    for(int i=n-1;~i;--i)
        f[i]=f[i+1]+n/(n-i),
        g[i]=g[i+1]+f[i+1]+f[i]*i/(n-i)+n/(n-i);
    printf("%.2f\n",g[0]);
    return 0;
}

计算平方的期望:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}
double f[N],h[N],n;

signed main(){
    scanf("%lf",&n);
    for(int i=n-1;~i;--i)
        f[i]=f[i+1]+n/(n-i),
        h[i]=h[i+1]+2*f[i+1]+1+(2*i*f[i]+i)/(n-i);
    printf("%.2f\n",(h[0]+f[0])/2);
    return 0;
}
posted @ 2022-05-12 15:11  _Famiglistimo  阅读(43)  评论(0编辑  收藏  举报