BZOJ4008:[HNOI2015]亚瑟王 (概率DP)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4008


题目分析:一道很厉害的DP,做法和背包类似。记g[i][j]表示前i张卡牌,有j张卡牌发动了技能的概率,那么存在如下转移:

g[i+1][j]=g[i][j](1p[i+1])rj

g[i+1][j+1]=g[i][j](1(1p[i+1])rj)

转移时再用一个f数组记录期望即可。

为什么第二条式子乘以的是r-j次机会中至少选一次的概率,而不是刚好选一次的概率呢?我一开始也有些疑惑,后来看了这篇blog才明白。这是因为选了第一次之后,后面的回合会以1的概率跳过它。

代码很短,大概是我近期写过的最短的代码了。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=233;
typedef long double LD;

LD f[maxn][maxn];
LD g[maxn][maxn];
LD pk[maxn][maxn];

LD p[maxn];
int d[maxn];
int t,n,r;

int main()
{
    freopen("4008.in","r",stdin);
    freopen("4008.out","w",stdout);

    scanf("%d",&t);
    while (t--)
    {
        memset(f,0.0,sizeof(f));
        memset(g,0.0,sizeof(g));
        g[0][0]=1.0;

        scanf("%d%d",&n,&r);
        for (int i=1; i<=n; i++)
            scanf("%Lf%d",&p[i],&d[i]),p[i]=1.0-p[i],pk[i][0]=1.0;
        for (int i=1; i<=n; i++)
            for (int j=1; j<=r; j++)
                pk[i][j]=pk[i][j-1]*p[i];

        for (int i=0; i<n; i++)
            for (int j=0; j<=r; j++)
            {
                g[i+1][j]+=(g[i][j]*pk[i+1][r-j]);
                f[i+1][j]+=(f[i][j]*pk[i+1][r-j]);
                if (j==r) continue;

                g[i+1][j+1]+=(g[i][j]*(1.0-pk[i+1][r-j]));
                f[i+1][j+1]+=((f[i][j]+(LD)d[i+1]*g[i][j])*(1.0-pk[i+1][r-j]));
            }

        LD ans=0.0;
        for (int i=0; i<=r; i++) ans+=f[n][i];
        printf("%.10Lf\n",ans);
    }

    return 0;
}
发布了160 篇原创文章 · 获赞 76 · 访问量 10万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览