[BZOJ1426]收集邮票 题解
期望 DP
学习自:Miracle ,非常强大
Statement
\(n\) 种邮票,每种有无数张,收集到的概率都是 \(1/n\) ,第 \(i\) 次收集代价 \(i\) ,问收集所有类型邮票的期望代价
\(n\le 10^4\)
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]\) 的时候是第一次收集,有这样一个式子
(你不就是把 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\) 步数平方的期望,有
哈哈,递推就好了
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;
}