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 ] = g [ i ] [ j ] ∗ ( 1 − p [ i + 1 ] ) r − j

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

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

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

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


CODE:

#include
#include
#include
#include
#include
#include
#include
#include
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; ifor (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;
}

你可能感兴趣的:(DP)