题目描述:
现在有A、B面条各有N碗,用投掷硬币的方式来决定吃哪种面条。当只剩下一种面条时不需要再投掷硬币。输入N,要求将所有面条吃完投掷硬币的数学期望。(N<=1000)
分析:
这个短语一定要认识:mathematical expectation(数学期望)。
回忆求数学期望的一般方法,E=∑ pi×xi。即所有可能的取值×取值的概率然后求和。
我们只考虑把A面条吃完,剩下若干B面条的情况,得出的结果乘以2即可。记组合数c(i,n)为c[n][i];记2的n次幂为pow2[n]。
那么吃完n碗A面条,吃了i碗B面条(0<=i<n)时的概率为:c[n-i-1][i] / pow2[n+i];这种情况下投掷硬币的次数为n+i;
那么总的期望公式为:E = 2 × ∑( ( c[n-i-1][i] / pow2[n+i] ) × ( n+i ) ) , (0<=i<n)
可以这样理解:总共吃了n+i碗面条,一共要投掷n+i次硬币,所以一共有pow2[n+i]种可能性。而满足吃完n碗A,吃了i碗B的条件,必须最后一碗是吃A面条(因为结束投掷硬币是在A刚好吃完的时候),所以满足条件的情况总数为,前n-1个A和i个B的全排列,也就是n-i-1个位置选i个放置B,其余放置A,共c[n-i-1][i]种情况。
那么期望值为,所有情况的投掷硬币次数×概率之和,再乘以2。
到这里,可以说题目刚刚完成了一半。由于n的范围是1000,组合数c[n][i]和2的n次幂pow2[n]都会很大。是不是需要用大数呢?
分析最后的输出结果只要求保留两位小数,答案也不会超过2000。也就是说,我们尝试用double直接算,只要保证运算过程中不超出范围,最后输出的精度一定是足够的。
查了一下double的取值范围,大约是±1e308,大约是2的1024次方。
单看pow2[n+i],n+i的取值是2000,这就已经超范围了。仔细观察,其实公式可以变形,将pow2[n]提到公式外面:
E = ( 2 / pow2[n] ) × ∑( ( c[n-i-1][i] / pow2[i] ) × ( n+i ) ) , (0<=i<n)
这样,公式外面和里面的pow2[]数组都在double范围内了。但是尝试了一下用杨辉三角的方式计算c[n][i],到后面的时候还是超了……
和SHP折腾了半天,这里还可以优化一下。观察公式,由于c[x][i]对应的项一定会除以pow2[i],那么就可以在计算c[x][i]的时候边算边除。也许就不会超范围了。
杨辉三角公式,c[n][i] = c[n-1][i] + c[n-1][i-1]。
现在新的c[n][i]保存的是c[n][i] / pow2[i],变形公式:c[n][i] = c[n-1][i] + c[n-1][i-1] / 2。
为什么只在c[n-1][i-1]这项除2?因为按照新的定义方式,c[n-1][i]已经除过pow2[i],c[n-1][i-1]已经除过pow2[i-1],要使c[n][i]的分母是pow2[i],只要在c[n-1][i-1]这一项除2即可。
到这里,题目基本上完美解决了(实际推导过程花了不少时间)……
现在用新定义的c[n][i]来描述最后的公式为:
E = ( 2 / pow2[n] ) × ∑( c[n-i-1][i] × ( n+i ) ) , (0<=i<n)
花絮:
这是周六和SHP在励剑做一套的ZJU校赛题目,主要为了准备珠海赛热身。
前三个水题大约在2小时的时候做掉了,剩下大约3小时的时间一直在推这个题,百般波折,公式还推错了几次……
SHP最初的做法没有事先计算c[n][i]而是实时计算,时间复杂度增加到了O(n^3),而实际只需要O(n^2),虽然答案正确,但还是没摆脱TLE的命运……
我按照我的方式改写了一遍,一遍改写一遍和SHP一起修正公式,在快到5小时时候终于AC掉了~一阵狂喜……
粗略看了一下排名,200人的比赛我们总共4题,应该是在40名左右,可以拿银奖了。很高兴,去鸿兴源大吃一顿……
虽然程序AC了,但是看现场比赛的情况,也有好几个队伍是很快就过掉了,不知是有更优秀的公式呢?还是ZJU的大牛们推这种公式就是家常便饭……迷茫ing,期待更好的解法。。
---------------------------------------------------------------------------------------------------------
#include <stdio.h>
#define N 1005
double c[N+N][N+N];
double pow2[N];
double ans[N];
int main()
{
int i,j,k,m,n,T;
//pow2[]
pow2[0]=1;
for(i=1;i<N;i++){
pow2[i]=pow2[i-1]*2.0;
}
//c[][]
for(i=0;i<N;i++){
c[i][0]=1;
c[i][i+1]=0;
}
for(i=1;i<N+N;i++){
for(j=1;j<=i;j++){
c[i][j]=c[i-1][j-1]/2+c[i-1][j];
}
}
//ans[]
for(n=1;n<=1000;n++){
ans[n]=0;
for(i=0;i<n;i++){
ans[n]+=(c[n+i-1][i])*(n+i);
}
ans[n]/=pow2[n-1];
}
scanf("%d",&T);
while(T--){
scanf("%d",&n);
printf("%.2lf/n",ans[n]);
}
return 0;
}