NOIP第二轮模拟 游戏

【问题描述】
Alice和Bob两个人正在玩一个游戏,游戏有很多种任务,难度为p的任务(p是正整数),有1/(2^p) 的概率完成并得到1/(2^(p−1))分,如果完成不了,得0分。一开始每人都是0分,从Alice开始轮流做任务,她可以选择任意一个任务来做;而Bob只会做难度为1的任务。只要其中有一个人达到n分,即算作那个人胜利。求Alice采取最优策略的情况下获胜的概率。
【输入】
一个正整数n,含义如题目所述。
【输出】
一个数,表示Alice获胜的概率,保留6位小数。
【样例1】
1 0.666667
【样例2】
2 0.651852
【数据规模】
对于30%的数据,n≤10
对于100%的数据,n≤500


【分析】
又是一道神奇的概率题。。。
这里因为小B只有1或0两种情况且两种情况各为1/2,所以可以把小A和小B合并起来一起讨论。这里定义一个函数f(x,y)表示小A还差x分到达目标分数,小B还差y分到达目标分数时小A获胜的概率。这里先假设小A此时做难度为p任务,那么概率分布可以如下图所表示
这里写图片描述
那么就有f(x,y)=(1/(2^(p+1)))f(x-2^(p-1),y-1)+(1/(2^(p+1)))f(x-2^(p-1),y)+((2^p-1)/(2^(p+1)))f(x,y-1)+((2^p-1)/(2^(p+1)))f(x,y)
(真的非常长)

化简后f(x,y)=(f(x-2^(p-1),y-1)+f(x-2^(p-1),y)+f(x,y-1)*(2^p-1))/(2^p+1);
因为不确定p为何值,所以直接进行枚举
边界情况如果x<=0,那么f(x,y)=1;
如果y<=0(隐含x>0),那么f(x,y)=0;

整个过程可以用记忆化来大大减少时间复杂度。

下方看代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=510;
double ans;
double f[maxn][maxn];
bool flag[maxn][maxn];
int n;
double dfs(int x,int y)
{
    if(x<=0) return 1;
    if(y<=0) return 0;
    if(flag[x][y]) return f[x][y];
    int p=0;
    while(1<<p<=x) p++;
    p++;//一次性达到目标分数
    for(;p>=1;p--)
        f[x][y]=max(f[x][y],(dfs(x-(1<<p-1),y-1)+dfs(x-(1<<p-1),y)+dfs(x,y-1)*((1<<p)-1))/((double)(1<<p)+1));
    //利用方程递归得到结果
    flag[x][y]=1;//记忆化
    return f[x][y];
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    memset(flag,0,sizeof(flag));
    cin>>n;
//  for(int i=0;i<=n;i++)
//      f[i][0]=0,flag[i][0]=1;
//  for(int i=0;i<=n;i++)
//      f[0][i]=1,flag[0][i]=1;
//原用于确定边界,但有可能小A一次性取一个比目标分数更大的数所以这里废除了
    dfs(n,n);//记忆化搜索
    printf("%.6lf\n",f[n][n]);//输出六位答案
    return 0;
}

如有不足之处,欢迎指正。

猜你喜欢

转载自blog.csdn.net/xyc1719/article/details/81132570