动态规划(Hearthstone,HDU 5816)

看到N,M都很小,一开始想到了暴力搜索+剪枝可以枚举出能够完成击杀的牌的组合,计算量最多为2^20=1e6,(看到网上用状压来枚举,虽然不带剪枝,但是常数应该更小)。

然后在深入考虑预估函数的剪枝效率时,发现P也很小,好像可以动态规划。

dp[i][j][k]表示对于前i张火球术,恰选j张,恰打出k点伤害的概率,dp完后后缀和一下就可以得到前i张火球术,恰选j张,打出大于等于k点伤害的概率。

这样∑dp[M][j][P]就是能够击杀的概率。

但是这样还不够,因为奥术智慧的数量会影响到你的摸牌。

如果没有奥术智慧,那么你只能打出一张火球术。

如果全都是奥术智慧,那么你打不出任何伤害。

适当数量的奥术智慧才能最大化伤害。

然后发现,摸牌的情况就那么几种。

要么就有牌没法摸了,要么就全模完了。

就是考虑把奥术智慧全部打出去,然后摸牌,直到没有奥术智慧了。

然后把火球术再一次性打出去。

情况:

共摸了1张牌,其中0张奥数智慧,结束。

共摸了3张牌,其中1张奥数智慧,结束。

共摸了5张牌,其中2张奥数智慧,结束。

。。。

共摸了N+M张牌,其中N张奥数智慧,结束。

所以dp[i][j]表示前i张牌有j张奥数智慧的概率。

然后看情况转移就好了。(允许摸牌,有牌可摸,乘以概率)


最后答案就是∑上述各种情况的概率乘以击杀的概率,最后一种情况不要算重了。

或者说就是枚举摸到k张火球术的概率乘以k张火球术能击杀的概率。

或者说就是枚举摸牌结束的情况,就知道有k张火球术,然后乘以k张击杀的概率。


后来发现dp[i][j][k]不好算概率,就决定dp方案数,然后除以组合数。

dp也不用求什么后缀和,直接把伤害大于等于P的方案数累加到伤害等于P的方案数中就好了。


最后情况有三种

1、奥数智慧不够用,全拿到都摸不完。

2、奥数智慧刚刚好,全拿到恰好全摸完。

3、奥数智慧太多了,全拿到打出去也摸不到牌了。


只有第3中情况我们需要额外算全部摸完的情况。


一开始dp[i][j][k]计算概率,写完后发现转移是错的,也没法转移,后来才改成方案数。

然后交上去RE,说是除零,分析后发现是最后枚举的时候没考虑到奥术智慧或者火球术不够多,除以了错误的数据,比如0。

改正后交上去WA,后来全部重新思考了一遍,发现是全摸完的情况没讨论对,所以算重或算漏了。

最后才AC。时间复杂度O(NMP),0ms过了。


希望以后能思考得更具体一点,比如说具体到dp该怎么转移,是否正确,最后答案该怎么计算,或不会重复或遗漏,然后再开始写代码。


代码

#include
#include
using namespace std;
typedef long long ll;
const ll maxc = 25;
const ll maxp = 1010;

ll gcd(ll a,ll b)
{
    return !b?a:gcd(b,a%b);
}

struct fs
{
    ll fz,fm;
    fs(ll fz=0,ll fm=1):fz(fz),fm(fm)
    {
        this->yuefen();
    }
    void zero()
    {
        fz=0;
        fm=1;
    }
    void one()
    {
        fz=1;
        fm=1;
    }
    void yuefen()
    {
        ll GCD = gcd(fz,fm);
        fz/=GCD;
        fm/=GCD;
    }
    fs operator + (const fs& rhs) const
    {
        fs ret;
        ll GCD = gcd(fm,rhs.fm);
        ret.fm=fm/GCD*rhs.fm;
        ret.fz=rhs.fm/GCD*fz+fm/GCD*rhs.fz;
        ret.yuefen();
        return ret;
    }
    fs operator * (const fs& rhs) const
    {
        fs ret;
        ret.fm=fm*rhs.fm;
        ret.fz=fz*rhs.fz;
        ret.yuefen();
        return ret;
    }
    fs operator - (const fs& rhs) const
    {
        fs ret;
        ll GCD = gcd(fm,rhs.fm);
        ret.fm=fm/GCD*rhs.fm;
        ret.fz=rhs.fm/GCD*fz-fm/GCD*rhs.fz;
        ret.yuefen();
        return ret;
    }
    fs operator / (const fs& rhs) const
    {
        fs ret;
        ret.fm=fm*rhs.fz;
        ret.fz=fz*rhs.fm;
        ret.yuefen();
        return ret;
    }
    void prll()
    {
        printf("%lld/%lld\n",fz,fm);
    }
};

ll P,N,M;
ll X[maxc];
fs num[maxc][maxc];
ll kill[maxc][maxc][maxp];
ll C[maxc][maxc];

void read()
{
    scanf("%lld %lld %lld",&P,&N,&M);
    for(ll i=1;i<=M;i++)
        scanf("%lld",X+i);
}

void getnum()
{
    for(ll i=0;i<=N+M;i++)
        for(ll j=0;j<=i;j++)
            num[i][j].zero();
    num[0][0].one();
    for(ll i=0;iN) break;
        if(i-j>M) break;
        ans=ans+num[i][j]*fs(kill[M][i-j][P],C[M][i-j]);
    }
    if(N>=M)
    {
        ll i = N+M;
        ll j = N;
        ans=ans+num[i][j]*fs(kill[M][M][P],C[M][i-j]);
    }
    ans.prll();
}

int main()
{
    getC();
    //prllC();
    ll T;
    scanf("%lld",&T);
    while(T--) solve();
    return 0;
}


你可能感兴趣的:(动态规划,概率DP,计数DP)