收集邮票 (概率dp)
收集邮票 (概率dp)
题目描述
有 \(n\) 种不同的邮票,皮皮想收集所有种类的邮票。唯一的收集方法是到同学凡凡那里购买,每次只能买一张,并且买到的邮票究竟是 \(n\) 种邮票中的哪一种是等概率的,概率均为 \(\frac{1}{n}\) 。但是由于凡凡也很喜欢邮票,所以皮皮购买第 \(k\) 张邮票需要支付 \(k\) 元钱。 现在皮皮手中没有邮票,皮皮想知道自己得到所有种类的邮票需要花费的钱数目的期望。
输入格式
一行,一个数字 \(N,N\leqslant 10000\)
输出格式
要付出多少钱. 保留二位小数
样例
样例输入
3
样例输出
21.25
数据范围与提示
\(N\leqslant 10000\)
分析
按照概率 \(dp\) 的套路,我们反向定义方程,反着推,定义 \(f[i]\) 为已经有了 \(i\) 种,还需要买几次。 \(g[i]\) 为已经有了 \(i\) 种,还需要多少钱。
因为当前已经有了 \(i\) 种了,每种选的可能性相同,所以这一次选重复的概率为 \(\frac{i}{n}\) ,此时的次数就是 \(f[i] + 1\) ,因为当前拿了一个重复的,所以还要多拿一次,所以加一。
不重复的概率就是 \(\frac{n-i}{n}\),次数就是 \(f[i+1] + 1\),因为没拿重复的,所以是拿了 \(i+1\) 种的步数加一。那么 \(f[i]\) 的转移就是:
\[f[i] = (f[i] + 1) \times \frac{i}{n} + (f[i+1] + 1) \times \frac{n-i}{n}
\]
化简一下就是:
\[f[i] = f[i+1] \times \frac{n}{n-i}
\]
接下来考虑钱数的转移,每一次增加的价格就是取的次数,而拿重复的概率是 \(\frac{i}{n}\),所以这部分就是 \((g[i]+f[i]+1)\times \frac{i}{n}\)。
其次就是没有重复,那么这部分就是 \((g[i+1]+f[i+1]+1)\times \frac{n-i}{n}\)
所以总的就是 :
\[g[i] = (g[i]+f[i]+1)\times \frac{i}{n} + (g[i+1]+f[i+1]+1)\times \frac{n-i}{n}
\]
化简完就是:
\[g[i] = \frac{i}{n-i}\times f[i] + g[i+1] + f[i+1] + \frac{n}{n-i}
\]
然后倒着枚举,转移就很简单了。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int L = 1 << 20;
char buffer[L],*S,*T;
#define getchar() (S == T &&(T = (S = buffer) + fread(buffer,1,L,stdin),S == T) ? EOF : *S++)
const int maxn = 1e5 + 10;
double f[maxn],g[maxn];
inline int read(){
int s = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')f = -1;
ch = getchar();
}
while(isdigit(ch)){
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
int main(){
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
int n = read();
for(int i = n - 1; ~i ; --i){
f[i] = f[i+1] + (1.0 * n) / (1.0 * (n - i));
g[i] = (1.0 * i) / (1.0 * (n - i)) * (f[i] + 1) + g[i+1] + f[i+1] + 1;
}
printf("%.2lf",g[0]);
return 0;
}
$Never\ Give\ Up$