UVa 1639 - Candy(数学期望 + 精度处理)
链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4514
题意:
有两个盒子各有n(1≤n≤2e5)个糖,每天随机选一个(概率分别为p,1-p),然后吃一颗糖。
直到有一天,打开盒子一看,没糖了!输入n,p,求此时另一个盒子里糖的个数的数学期望。
分析:
根据期望的定义,不妨设最后打开第1个盒子,此时第2个盒子有i颗,则这之前打开过n+(n-i)次盒子,
其中有n次取的是盒子1,其余n-i次取的盒子2,概率为C(2n-i,n)(p^(n+1))((1-p)^(n-i))。
注意p的指数是n+1,因为除了前面打开过n次盒子1之外,最后又打开了一次。
这个概率表达式在数学上是正确的,但是用计算机计算时需要小心:
n可能高达20万,因此C(2n-i,n)可能非常大,而(p^(n+1))和((1-p)^(n-i))却非常接近0。
如果分别计算这3项再乘起来,会损失很多精度。
一种处理方式是利用对数,设v1(i) = ln(C(2n-i,n)) + (n+1)ln(p) + (n-i)ln(1-p),
则“最后打开第1个盒子”对应的数学期望为e^v1(i)。
同理,当最后打开的是第2个盒子,对数为v2(i) = ln(C(2n-i,n)) + (n+1)ln(1-p) + (n-i)ln(p),概率为e^v2(i)。
根据数学期望的定义,最终答案为sum{i(e^v1(i)+e^v2(i))}。
代码:
1 #include <cstdio> 2 #include <cmath> 3 4 const int UP = 2e5 * 2 + 5; 5 long double logF[UP]; 6 7 // C(n,m) = n!/(m!(n-m)!) 8 long double logC(int n, int m) { 9 return logF[n] - logF[m] - logF[n-m]; 10 } 11 12 double solve(int n, double p) { 13 double ans = 0; 14 for(int i = 0; i <= n; i++) { 15 long double c = logC(n+n-i, n); 16 long double v1 = c + (n+1)*log(p) + (n-i)*log(1-p); 17 long double v2 = c + (n+1)*log(1-p) + (n-i)*log(p); 18 ans += i * (exp(v1) + exp(v2)); 19 } 20 return ans; 21 } 22 23 int main() { 24 logF[0] = 0; 25 for(int i = 1; i < UP; i++) logF[i] = logF[i-1] + log(i); 26 int n; 27 double p; 28 for(int cases = 1; ~scanf("%d%lf", &n, &p); cases++) { 29 printf("Case %d: %.6f\n", cases, solve(n, p)); 30 } 31 return 0; 32 }