题意:有两个箱子,里面各放有糖n个,每天从两个之中任取一颗糖,突然有一天小孩取糖时发现一个盒子里空了,问另一个盒子里面现在有多少糖(求期望值)?
#include<iostream> #include<cmath> using namespace std; double F[400008]; double C_N_M(int n,int m) { return F[n]-F[m]-F[n-m]; } void Init() { int i; F[0]=0; for(i=1;i<=400006;i++) F[i]=F[i-1]+log(i*1.0); } double sovle(int n,double p) { double sum; int k; for(sum=0,k=0;k<=n-1;k++) sum+=(n-k)*(exp((C_N_M(n+k,k))+(n+1)*log(p)+k*log(1-p))+ exp((C_N_M(n+k,k))+(n+1)*log(1-p)+k*log(p))); return sum; } int main() { double p; int n,t=0; Init(); while(cin>>n>>p) { printf("Case %d: %.6f\n",++t,sovle(n,p)); } return 0; }
参考自:http://blog.csdn.net/sylg_li/article/details/8208514和http://www.tuicool.com/articles/zInIja。
先说说我做这个题走过的的误区,我错误的以为题目里面说的那个the other box指的是取得概率为1-p那个,最后看了有一个解题报告的公式才知道算的是两个的期望值之和。这个题期望的公式好推,就是算排列组合的时候可能不知道问么算,取log()(这个是数学里面的ln可别以为是)饥渴,还原的时候用exp()。还有一点,第一个箱子为空时仍然要取一次糖,因为取不出来的时候才知道糖完了。
这个地方也要注意,为什么是C(n+k,k)而不是C(n+k+1,k)?k表示的是the other box取的糖的数目,我们在第一个箱子取了n+1次糖,可是那次取空时并没有取到糖果,所以我们算期望值时的概率还是要用C(n+k,k)
对于第一个数据溢出的问题,可以这样解决。因为组合数公式为:
C(n,m) = n!/(m!(n-m)!)
为了避免直接计算n的阶乘,对公式两边取对数,于是得到:
ln(C(n,m)) = ln(n!)-ln(m!)-ln((n-m)!)
进一步化简得到:
这样我们就把连乘转换为了连加,因为ln(n)总是很小的,所以上式很难出现数据溢出。
为了解决第二个效率的问题,我们对上式再做一步化简。上式已经把连乘法变成了求和的线性运算,也就是说,上式已经极大地简化了计算的复杂度,但是还可以进一步优化。从上式中,我们很容易看出右边的3项必然存在重复的部分。现在我们把右边第一项拆成两部分:
这样,上式右边第一项就可以被抵消掉,于是得到:
上式直接减少了2m次对数计算及求和运算。但是这个公式还可以优化。对于上面公式里的求和,当m<n/2时,n-m是一个很大的数,但是当m>n/2时,n-m就会小很多。我们知道:
C(n,m) = C(n,n-m)
那么通过这个公式,我们可以把小于n/2的m变为大于n/2的n-m再进行计算,结果是一样的,但是却能减少计算量。
当计算出ln(C(n,m))后,只需要取自然对数,就可以得到组合数:
C(n,m) = exp(ln(C(n,m)))
用这种方法计算组合数,如果只计算ln(C(n,m))的话,n可以取到整型数据的极限值65535,
HDU ACM 4465 Candy->概率-数学期望-排列组合
原文地址:http://blog.csdn.net/a809146548/article/details/46420017