zoukankan      html  css  js  c++  java
  • 洛谷 P2473 [SCOI2008]奖励关 解题报告

    P2473 [SCOI2008]奖励关

    题目描述

    你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关。在这个奖励关里,系统将依次随机抛出(k)次宝物,每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再吃)。

    宝物一共有(n)种,系统每次抛出这(n)种宝物的概率都相同且相互独立。也就是说,即使前(k-1)次系统都抛出宝物1(这种情况是有可能出现的,尽管概率非常小),第(k)次抛出各个宝物的概率依然均为(1/n)

    获取第(i)种宝物将得到(P_i)分,但并不是每种宝物都是可以随意获取的。第i种宝物有一个前提宝物集合(S_i)。只有当(S_i)中所有宝物都至少吃过一次,才能吃第(i)种宝物(如果系统抛出了一个目前不能吃的宝物,相当于白白的损失了一次机会)。注意,(P_i)可以是负数,但如果它是很多高分宝物的前提,损失短期利益而吃掉这个负分宝物将获得更大的长期利益。

    假设你采取最优策略,平均情况你一共能在奖励关得到多少分值?

    输入输出格式

    输入格式:

    第一行为两个正整数(k)(n),即宝物的数量和种类。以下(n)行分别描述一种

    宝物,其中第一个整数代表分值,随后的整数依次代表该宝物的各个前提宝物(各宝物编号为1到(n)),以0结尾。

    输出格式:

    输出一个实数,保留六位小数,即在最优策略下平均情况的得分。

    说明

    (1<=k<=100, 1<=n<=15),分值为([-10^6,10^6])内的整数。


    想做这个题得先弄懂条件概率

    简单一点的解释是,B在A发生的条件下发生的概率。

    举个栗子,掷色子第一次投6概率为1/6,为A事件,第二次投6概率仍为1/6,为B事件。如果把两次投掷产生的一个结果算成一个最终状态,那么连续的状态AB发生的概率为1/36,也即是B在A发生的条件下发生的概率。

    条件概率一定得把连续的事件划为一个状态来求解

    对于具体题目来看,在第(i)次出现宝物的时候,我们产生的状态空间的大小即为(1/n^i)。对于其中每一个状态空间的延长我们都可以做出选和不选的决策(当然,有时候是强制不能选的),以保证最优策略。

    当然,即使没有决策,我们也不能找到所有状态空间进行统计,我们发现,第(i)个阶段产生的某一个状态空间对第(i+1)个阶段的每一个可能发生的宝物都能产生一个递推,这可能出现的(n)个宝物将状态空间扩大了(n)倍。

    于是我们实际上在统计的时候,对于第(i)个阶段宝物产生的状态空间,它在后面重复出现了(n^{k-i})次,所以这一维所有的答案产生的贡献最后需要除上(n^i),我们通过倒推来消除可能爆精度的问题(在后面具体提到)

    如果进行决策,我们利用背包的思想,将状态空间用一个新的状态表示处理,这也是转移方程中状态压缩的一维(j)(j)表示当前状态空间每个宝物是否出现。注意新的状态空间可能代表多个以往的状态空间。

    按照顺着的思想从前向后递推,我们用新状态空间对当前阶段每一个可能出现的概率进行递推,等价于原状态空间对每一个概率进行递推。这时候会产生两个问题,一是我们对每一个状态空间都得朴素的除上(n^i),会产生新的复杂度。二是我们需要额外的判断,保证统计答案时的合法性,比较麻烦。

    所以我们进行倒着做,可以对每一次产生的新状态都除以(n),而不必对每一个状态特殊判断。最后统计答案时也只有唯一的一个合法。

    方程:(dp[i][j])代表第(i)阶段(j)状态已经发生转移的最大分数。

    转移:(dp[i][j]+=sum_{l=1}^n max(dp[i+1][j|(1<<l-1)],dp[i+1][j])),(max)左边要判转移合法

    目标:(dp[1][0])

    可能说得不严谨,大概只是个人的一点浅显的感性理解,今天也是第一次做条件概率的题,如有不足,还请提出。


    Code:

    #include <cstdio>
    const int N=102;
    double dp[N][1<<15],score[18];
    double max(double x,double y){return x>y?x:y;}
    int n,k,pre[18];
    void init()
    {
        scanf("%d%d",&k,&n);
        int pree;
        for(int i=1;i<=n;i++)
        {
            scanf("%lf%d",score+i,&pree);
            while(pree)
            {
                pre[i]|=1<<pree-1;
                scanf("%d",&pree);
            }
        }
    }
    void work()
    {
        for(int i=k;i;i--)
            for(int j=0;j<=1<<n;j++)
            {
                for(int l=1;l<=n;l++)
                {
                    if((pre[l]&j)==pre[l])
                        dp[i][j]+=max(dp[i+1][j|(1<<l-1)]+score[l],dp[i+1][j]);
                    else
                        dp[i][j]+=dp[i+1][j];
                }
                dp[i][j]/=double(n);
            }
        printf("%.6lf",dp[1][0]);
    }
    int main()
    {
        init();
        work();
        return 0;
    }
    
    

    2018.7.3

  • 相关阅读:
    一个数组找出第k大的数(待补)
    变动二叉树
    判断一个二叉树
    Redis的过期策略和内存淘汰机制
    sql连接详解
    http 请求和格式
    java基础知识
    分页信息
    持续集成之Jenkins自动部署war包到远程服务器
    no-sql数据库之redis
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9260472.html
Copyright © 2011-2022 走看看