[BZOJ1426]收集邮票 题解

期望 DP

学习自:Miracle ,非常强大

Statement

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

n104

fzoi.top

Solution

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

具体的,f[i]=nin(f[i+1]+1)+in(f[i]+1)

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

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

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

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

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

g[i]=nin(g[i+1]+f[i+1]+1)+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((s2+s)/2)=[E(s2)+E(s)]/2

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

h[i] 表示从 in 步数平方的期望,有

h[i]=ninE((s[i+1]+1)2)+inE((s[i]+1)2)=nin(h[i+1]+2×f[i+1]+1)+in(h[i]+2×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 @   _Famiglistimo  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示