题意:Ben Bill生日,邀请朋友去m记。有2n个人(包括Ben Bill)。n个汉堡包和n个乳酪汉堡。从Bill左边的女孩开始分汉堡,一直往左,最后转一圈回到Ben 和 Bill 。分汉堡的方法是用一枚硬币,每个人扔出正面就拿汉堡,反面就拿另一种。求出Ben 和Bill 能拿到同一种汉堡的概率。n<=10W
思路:
只要两种汉堡都剩下至少一个,能拿到其中一种的概率都是0.5。本题可以先求二人刚好分得不同的汉堡(就是前2n-2个人有n-1个拿到一种汉堡,另外n-1个拿到另外一种)的概率,在用1减去。
p = 1 – C (2n-2 , n-1) * 0.5 2n-2 (2n-2重伯努利实验满足二项分布。)
不过2n可以高达10W,对于组合排列数C来说。10W的结果数量级大概是10W ! 的一半。十分庞大,double根本装不下。即使可以使用高精度浮点模板,如此高的精度也使得时间复杂度十分大。而0.5的10W次方又是一个相当小的数字,精度过高。如果用double储存会直接被当做无穷小也就是零了。不过如果二者做乘积,结果数量级抵消。所以现在面临的问题是中间结果太大。
这种情况通常可以通过分步运算,保持数据在可控的范围内。 C (2n-2 , n-1) = (2n-2)! / ( (n-1) ! *( n-1) ! )这样可以分步,先乘分子的一部分,再除分母的一部分。结果可以保证准确。不过很遗憾,10W次不断的浮点乘除,巨大的时间复杂度还是不能通过数据。
利用log函数可以将数据的数量级大大减少。10的10W次方经过log函数处理,结果也就是10W左右。
结果可以这样写
p = 1 – 10 a
a= log10 C (2n-2 , n-1) +(2n-2) * log10 0.5
= log10(2n-2) ! -log10 (n-1)! -log10(n-1) ! ) + (2n-2) * log10 0.5.
可以预先求出1~10W 阶乘对10取对数的结果,这样在程序中可以直接调用!代入能直接得出结果。加上一开始预处理求1~10W 阶乘对10取对数的结果。这是相当大的一个优化了。
#include<iostream>
#include<cstdio>
#include<limits.h>
#include<cmath>
#include<cstdlib>
#include<cstring>
using namespace std;
double log_n[100000];
const double aa = log(0.5);
int main(){
int i,j,t;
double ans ;
log_n[0] = 0;
for(i=1;i<=100000;i++){ // 可以利用之前的结果递推a[i] = log(i!) = log((i-1)!*i) = a[i-1] + log(i);
log_n[i] = log_n[i-1] + log(i) ;
}
scanf("%d",&t);
while(t--){
scanf("%d",&i);
j = i/2;
ans = log_n[i-2] - log_n[j-1] - log_n[j-1];
ans += (i - 2) * aa;
ans = exp(ans);
printf("%.4lf\n",1-ans);
}
return 0;
}