题目链接:Beating the Dataset - LightOJ 1274 - Virtual Judge (ppsucxtt.cn)
简化版题意:某人在蒙题,题目答案只有yes和no两种答案,总共有n道题,一开始你知道了n道题中答案为yes的题目数,你每蒙一道题之后,系统会告诉你正确答案是什么。你遵循的蒙题策略如下:
(1)第一道题目你一定蒙yes
(2)从第二道题目起,你每次蒙的答案都是上一道题的答案,比如说我现在做到了第i题,第i-1题的结果是no,那我第i题就直接蒙no
求你蒙错题的数量的期望值。
题目分析:这道题目我做了很长时间,每次读一遍题就得到一些新的信息(+_+),首先需要知道的一个信息就是答案是随机分布的,下一次的答案取决于当前所剩的yes和no的数量,其次需要挖掘的隐含条件就是比如总共有cntyes个题目是yes,你在第i道题目时恰好碰到了最后一个yes,按照正常人的思维就应该知道之后题目的答案都是no,但是由于第i道题目的答案是yes,所以你第i+1道题目还是会蒙yes,服了~
我们可以用动态规划来解决这道题目,设f[i][j][k]表示你当前做到了第i道题目,第i道及之前的题目中有j道题目正确答案是yes,第i道题目的正确答案是yes/no(k=1/0),利用这个含义我们就可以进行递推了,递推分析如下:
设n道题目中答案为yes的题目一共有cntyes道
j+1<=cntyes时:(意味着之后的答案还可能是yes)
f[i][j][0]代表着第i次答案为no,则我下一次会蒙no,下次答案为yes的概率为(cntyes-j)/(n-i),也就是说这是我下一次蒙错的概率,我下一次蒙对的概率就是1-蒙错概率
f[i][j][1]代表着第i次答案为yes,则我下一次会蒙yes,下次答案为yes的概率为(cntyes-j)/(n-i),也就是说这是我下一次蒙对的概率,我下一次蒙错的概率就是1-蒙对概率
j==cntyes时:(意味着之后的答案全是no)
f[i][j][0]代表着第i次答案是no,因为之后的答案全是no,则我下次一定蒙对,则有f[i][j][0]=f[i+1][j][0]
f[i][j][1]代表着第i次答案是yes,因为之后的答案全是no,则我下次一定蒙错,则有f[i][j][1]=f[i+1][j][0]+1
第一次答案是yes的可能性是cntyes/n,这也就是我第一次蒙对的概率,则我蒙错的概率就是1-cntyes/n,则答案就是f[1][1][1]*cntyes/n+(f[1][0][0]+1)*cntno/n
最后需要注意的时,由于空间原因,所以要开滚动数组
下面是代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=5e3+10;
double f[2][N][2];//表示该状态后面错误次数的期望值
//f[i][j][k]第一维表示当前处理到第i个回答
//第二维表示第i个位置及之前的题目答案中已经出现了j个YES
//第三维表示第i位答案是YES/NO(1/0)的情况下
int main()
{
int T,n,m;
cin>>T;
for(int o=1;o<=T;o++)
{
scanf("%d%d",&n,&m);
int cntyes=m-2*n;
int cntno=n-cntyes;
f[n&1][cntyes][0]=f[n&1][cntyes][1]=0;//终止态
for(int i=n-1;i>=1;i--)
{
int minyes=max(0,i-cntno);
int maxyes=min(i,cntyes);
for(int j=minyes;j<=maxyes;j++)
{
double pyes=1.0*(cntyes-j)/(n-i);//下一次答案为yes的概率
double pno=1-pyes;//下一次答案为no的概率
if(j+1<=cntyes)//第i次之后的答案还可能出现yes
{
f[i&1][j][0]=pyes*(f[(i+1)&1][j+1][1]+1)+pno*f[(i+1)&1][j][0];//第i个位置答案是no,则下一次猜no,有pyes的概率猜错
f[i&1][j][1]=pyes*f[(i+1)&1][j+1][1]+pno*(f[(i+1)&1][j][0]+1);//第i个位置答案是yes,则下一次猜yes,有(1-pyes)的概率猜错
}
else//第i次之后的答案只能是no
{
f[i&1][j][0]=f[(i+1)&1][j][0];//第i个位置答案是no,则下一次猜no,一定猜对
f[i&1][j][1]=f[(i+1)&1][j][0]+1;//第i个位置答案是yes,则下一次猜yes,一定猜错,期望+1
}
}
}
double ans=f[1][1][1]*cntyes/n+(f[1][0][0]+1)*cntno/n;//第一次答案为yes的概率为cntyes/n,也就是说第一次猜对的概率为cntyes/n
printf("Case %d: %.10lf\n",o,ans);
}
return 0;
}