[HNOI2015] 亚瑟王 题解

概率期望 DP

Statement

[HNOI2015]亚瑟王

给定一排 n 个数 di,每一个数有 pi 的概率选中它,每个数只能选一次。规定一轮游戏从第一个数开始尝试选择,每当你选中一个数 ,将你的分数加上 di,结束这轮游戏。你一共可以进行 r 轮这样的游戏,问期望总分数。

T444,n220,r132,0<pi<1,0di1000

Solution

naive 想法 1:按照需要什么设什么的套路,直接设 dp[i][j][0/1] 表示进行了 i 轮游戏,第 j 个数选没有选的期望分数。但是问题在于选择 i 轮第 j 张牌的时候,需要前 i1 轮中没有用过 j ,且 i 轮前 j1 张牌全部 miss

naive 想法2:

碰到有些期望的题目,一定要先秉承着白嫖的思路,在贡献具有独立性的时候,能拆则拆

——wyb

发现每一张牌的贡献是独立的,依据期望的线性性,所以可以直接算每一张牌发生贡献的概率。

于是依然按照需要什么设什么的想法,设 dp[i][j] 表示前 i 轮,选中第 j 个数的概率

不假思索地,我们得到这样的转移式子:

dp[i][j]=dp[i1][j]+(1dp[i1][j])×p[j]×miss

其中 miss 表示在 i 轮中,前 j 张牌全都 在之前选过了/这次没有选中,所以

miss=dp[i1][j]+(1dp[i1][j])(1p[j])

在转移 dp[i] 的时候顺带着转移即可。

但是这样实际上也是错的,考虑把 miss 这个乘法式子拆开,发现存在长成 dp[i1][j]×dp[i1][j1] 之类的项,就违反了一轮只能选中一个的规则,GG

正解:

好的,不会做,去看题解

发现思维一直禁锢在一个奇怪的位置,就是必须要记录当前是几轮

但其实仔细思考一下发现记录这个轮数是完全没有必要的,因为对于一个 i 而言,我关注的是他前面有几个数被抽了,而并不关注具体是在那些轮数被选择的

(很可惜,在看了题解的状态后自己推式子的时候并没有摆脱上面所述的这样一个思维的禁锢)

在没有限制的条件下,也就是假如即使选中了一个数也可以继续选,那么显然选中第 i 的概率就是 i=0r1(1p)ip=1(1p)r

但是显然没有这么美好,我们显然需要在前面填一个系数

f[i][j] 表示 r 轮结束后,第 i 个数有 j 次选择机会的概率,那么式子变成了

k=1rf[i][k](1(1pi)k)

发现这个表述有点蠢,选择机会意义不明,所以正如前面所言,”对于一个 i 而言,我关注的是他前面有几个数被抽了“,那么设 dp[i][j] 表示 r 轮结束后,前 i 个数中,抽走 j 个的概率。

容易发现现在要计算的变成了 k=0r1dp[i1][k](1(1pi)rk)

考虑 DP 如何转移:

dp[i][j]={ dp[i1][j]×(1pi)rj dp[i1][j1]×(1(1pi)rj+1)

时刻注意 dp 状态的意义是在 r 轮结束之后

复杂度 O(tnr)

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 250;
const int M = 150;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}

double p[N],fp[N];
double pw[N][N],dp[N][N];
int d[N];
int T,n,r;

signed main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&r);
        memset(fp,0,sizeof(fp));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;++i)
            scanf("%lf%d",&p[i],&d[i]);
        for(int i=1;pw[i][0]=1,i<=n;++i)
            for(int j=1;j<=r;++j)
                pw[i][j]=pw[i][j-1]*(1-p[i]);
        dp[1][0]=pw[1][r],dp[1][1]=fp[1]=1-dp[1][0];
        for(int i=2;i<=n;++i)
            for(int j=0;j<=r;++j){
                fp[i]+=dp[i-1][j]*(1-pw[i][r-j]);
                dp[i][j]+=dp[i-1][j]*pw[i][r-j];
                if(j)dp[i][j]+=dp[i-1][j-1]*(1-pw[i][r-j+1]);
            }
        double ans=0;
        for(int i=1;i<=n;++i)
            ans+=fp[i]*d[i];
        printf("%.10lf\n",ans);
    }
    return 0;
}
posted @   _Famiglistimo  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示