[BZOJ4008] [HNOI2015]亚瑟王(概率dp)

传送门


HNOI的题真不是人做的。。
要求期望的话,根据期望=权值*概率,我们只要计算出一张牌在r轮中出现的总概率乘以d[i]即可

那么现在就要求出这个概率。

由于一张牌在这一轮是否被抽中只与之前的有关,那么我们就可以从上一次的状态中转移过来。

首先我们设第i张牌在r轮中出现的总概率为a[i]

我们发现直接计算出现的概率有些困难,所以我们可以计算他不出现的概率然后在用1减去他
换句话说:

我们知道一张牌在议一轮出现的概率为p[i]的话那么一轮不出现的概率就是:
(1p[i]) ( 1 − p [ i ] )
根据概率论,那么我们在r轮中都不出现的概率就是:
(1p[i])r ( 1 − p [ i ] ) r

那么第一张牌也就是a[0]是很容易求得的
a[0]=1(1p[0])r a [ 0 ] = 1 − ( 1 − p [ 0 ] ) r
那么我们或许会想当然的认为后面的概率也是这么算,那就错了!!
因为题目中有这样一句话

如果技能发动,则对敌方造成 di点伤害,并结束这一轮。

那么我们就需要再次设立未知数
设f[i][j]表示前i张牌已经出了j张的概率,那么
a[i]=rj=0f[i1][j](1(1p[i])rj)(i>0) a [ i ] = ∑ j = 0 r f [ i − 1 ] [ j ] ∗ ( 1 − ( 1 − p [ i ] ) r − j ) ( i > 0 )

那我们就可以在dp中把a[i]顺便统计
那么我们再来推一下状态转移方程,可以从两个地方转移过来:
f[i][j]+=(f[i1][j](1p[i])rj)+(f[i1][j1](1(1p[i])rj+1) f [ i ] [ j ] + = ( f [ i − 1 ] [ j ] ∗ ( 1 − p [ i ] ) r − j ) + ( f [ i − 1 ] [ j − 1 ] ∗ ( 1 − ( 1 − p [ i ] ) r − j + 1 )
前一个括号的情况是这一轮不选,那么概率是 (1p[i])rj ( 1 − p [ i ] ) r − j
后一个括号就是这一轮要选,那么概率是 (1(1p[i])rj+1) ( 1 − ( 1 − p [ i ] ) r − j + 1 )

我的代码里面学习大佬用了预处理幂,因为转移的过程中会有多次计算到一个幂的情况,可以用这个来加速


code:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
double f[250][150],a[250];//f[i][j]:第i张牌,第j轮出的概率,a[i]:第i张牌在r轮中出的总概率(最后乘上d[i]就是答案) 
double powp[250][150];
double p[250];
int d[250];
/*
a[1]=1-(1-p[1])^r
a[i]=sigma(j-i)f[i-1][j]*(1-(1-p[i])^r-j) (i>0)

f[i][j]+=f[i-1][j]*(1-p[i])^r-j(i>0)
f[i][j]+=f[i-1][j-1]*(1-(1-p[i])^r-j+1) (i>0,j>0)
*/
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int n,r;scanf("%d%d",&n,&r);
        for(int i=0;i<n;i++)
        {
            scanf("%lf%d",&p[i],&d[i]);
        }
        for(int i=0;i<n;i++)//计算(1-p[i])^j 
        {
            powp[i][0]=1;
            for(int j=1;j<=r;j++)
            {
                powp[i][j]=powp[i][j-1]*(1-p[i]);
            }
        }
        memset(a,0,sizeof(a));
        memset(f,0,sizeof(f));
        f[0][0]=powp[0][r];//第0轮当然不出
        f[0][1]=a[0]=1-(powp[0][r]);
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<=r;j++)
            {
                a[i]+=f[i-1][j]*(1-powp[i][r-j]);
                f[i][j]+=f[i-1][j]*powp[i][r-j];
                if(j>0) f[i][j]+=f[i-1][j-1]*(1-powp[i][r-j+1]);
            }
        }
        double ans=0;
        for(int i=0;i<n;i++)
        {
            ans+=a[i]*d[i];
        }
        printf("%.10f\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值