算法:英雄砍死怪兽的概率

题目描述

给定3个参数,N,M,K 怪兽有N滴血,等着英雄来砍自己
英雄每一次打击,都会让怪兽流失[0-M]的血量

到底流失多少?每一次在[0~M]上等概率的获得一个值
求K次打击之后,英雄把怪兽砍死的概率

题目解析

思路:死亡的次数 / 总的次数

暴力递归

总次数:

  • 每砍一刀,都有0~M种可能(每一刀会生成一颗十叉树)
  • 一共K刀,也就是一共有K次M+1的分支,也就是总的可能次数是 ( M + 1 ) K (M + 1)^K (M+1)K

死亡的次数:

  • 什么时候死亡呢?当没血了(rest)怪兽就会死亡
  • 问题是:rest可以为负数吗?如果没有砍足K刀,怪兽就死掉了,应该怎么处理呢?

回答:

  • 因为总次数中包含了rest < 0时的次数,所以rest可以为负数
  • 而且总次数包含了一定要砍足K刀,所以理论上我们应该继续处理
class Solution {
    int monster_die(int time, int hp, int M){
        if(time == 0){
            return hp <= 0 ? 1 : 0;
        }

        int ways = 0;
        for (int i = 0; i <= M; ++i) {
            ways += monster_die(time - 1, hp - i, M);
        }
        return ways;
    }
public:
    double KillMonster(int N, int M, int K){
        int die = monster_die(K, N, M);
        int all = std::pow(M + 1, K);
        return (double )die/(double )all;
    }
};

当然,我们可以进行优化:

  • 如果还有time刀没有砍但是怪兽已经死亡,那么剩下的分支总是可以提前算出来,为 ( M + 1 ) t i m e (M + 1)^{time} (M+1)time
class Solution {

    int monster_die(int time, int hp, int M){
        if(time == 0){
            return hp <= 0 ? 1 : 0;
        }

        if(hp <= 0){
            return std::pow(M + 1, time);
        }

        int ways = 0;
        for (int i = 0; i <= M; ++i) {
            ways += monster_die(time - 1, hp - i, M);
        }
        return ways;
    }
public:
    double KillMonster(int N, int M, int K){
        int die = monster_die(K, N, M);
        int all = std::pow(M + 1, K);
        return (double )die/(double )all;
    }
};

暴力递归改动态规划

(1)准备表。分析递归函数的可变参数

 int monster_die(int time, int hp, int M)
  • time:0~K
  • hp:0~N

所以要准备一个二维数组:

std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));

(2)返回值:

  • dp[K][N]/ all

(3)填表
(3.1)先初始化表:dp[0][0] = 1,dp[0][1…] = 0

  	  if(time == 0){
            return hp <= 0 ? 1 : 0;
        }

(3.2)填表

  • dp[time][dp] = dp[time - 1][hp - (0…M)]
  • 所以,应该从上到下,从左往右填写
    在这里插入图片描述
 		if(hp <= 0){
            return std::pow(M + 1, time);
        }

        int ways = 0;
        for (int i = 0; i <= M; ++i) {
            ways += monster_die(time - 1, hp - i, M);
        }
  • 又因为第0行已经初始化了,所以我们从第一行开始填写

在这里插入图片描述

class Solution {
public:
    double KillMonster(int N, int M, int K){
        std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
        dp[0][0] = 1;  //  [0 time][1....hp] = 0
        for (int time = 1; time <= K; ++time) {
            dp[time][0] = (long) std::pow(M + 1, time);   // [1...K][0 hp] =
            for (int hp = 1; hp <= N; ++hp) {
                long ways = 0;
                for (int i = 0; i <= M; ++i) {
                    if(hp - i <= 0){
                        ways += std::pow(M + 1, time - 1);
                    }else{
                        ways += dp[time - 1][hp - i];
                    }
                }
                dp[time][hp] = ways;
            }
        }

        int kill = dp[K][N];
        int all = std::pow(M + 1, K);
        return (double )kill/(double )all;
    }
};

优化枚举

  • d p [ 3 ] [ 0 ] = p o w ( M + 1 , 3 ) dp[3][0] = pow(M + 1, 3) dp[3][0]=pow(M+1,3)
  • d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 1 − M ) ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(1 - M)] dp[3][1]=dp[2][1]+dp[2][0,1,2......(1M)]
  • d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ( 1 − M ) ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(2 - M)(1 - M)] dp[3][1]=dp[2][1]+dp[2][0,1,2......(2M)(1M)]
  • d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] + d p [ 2 ] [ 1 − M ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(2 - M)] + dp[2][1-M] dp[3][1]=dp[2][1]+dp[2][0,1,2......(2M)]+dp[2][1M]
  • d p [ 3 ] [ 1 ] − d p [ 2 ] [ 1 − M ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] dp[3][1] - dp[2][1-M] = dp[2][1] + dp[2][0,-1, -2......(2 - M)] dp[3][1]dp[2][1M]=dp[2][1]+dp[2][0,1,2......(2M)]

然后:

  • d p [ 3 ] [ 2 ] = d p [ 2 ] [ 2 ] + d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] dp[3][2] = dp[2][2] + dp[2][1] + dp[2][0,-1, -2......(2 - M)] dp[3][2]=dp[2][2]+dp[2][1]+dp[2][0,1,2......(2M)]

从而:

  • d p [ 3 ] [ 2 ] = d p [ 2 ] [ 2 ] + d p [ 3 ] [ 1 ] − d p [ 2 ] [ 1 − M ] dp[3][2] = dp[2][2] + dp[3][1] - dp[2][1-M] dp[3][2]=dp[2][2]+dp[3][1]dp[2][1M]

推广:

  • d p [ t i m e ] [ h p ] = d p [ t i m e − 1 ] [ h p ] + d p [ t i m e ] [ h p − 1 ] − d p [ t i m e − 1 ] [ h p − 1 − M ] dp[time][hp] = dp[time - 1][hp] + dp[time ][hp - 1] - dp[time - 1][hp - 1 -M] dp[time][hp]=dp[time1][hp]+dp[time][hp1]dp[time1][hp1M]
    • h p − 1 − M > = 0 hp - 1 -M >=0 hp1M>=0时, d p [ t i m e − 1 ] [ h p − 1 − M ] dp[time - 1][hp - 1 -M] dp[time1][hp1M]存在
    • h p − 1 − M < 0 hp - 1 -M < 0 hp1M<0时, ( M + 1 ) t i m e − 1 (M + 1)^{time - 1} (M+1)time1

得到:

class Solution {
public:
    double KillMonster(int N, int M, int K){
        std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
        dp[0][0] = 1;  //  [0 time][1....hp] = 0
        for (int time = 1; time <= K; ++time) {
            dp[time][0] = (long) std::pow(M + 1, time);   // [1...K][0 hp] =
            for (int hp = 1; hp <= N; ++hp) {
                dp[time][hp] = dp[time][hp - 1] + dp[time - 1][hp];
                if(hp - 1 - M >= 0){
                    dp[time][hp] -= dp[time - 1][hp - 1 - M];
                }else{
                    dp[time][hp] -= std::pow(M + 1, time - 1);
                }
            }
        }

        int kill = dp[K][N];
        int all = std::pow(M + 1, K);
        return (double )kill/(double )all;
    }
};

测试

int main()
{
    std::vector<int> v = { 1,2,1,1,2,1,2 };

    Solution a;
    std::cout << a.KillMonster(5, 5, 5) << "\n";  //0.983796

思路:死亡概率 = 1 - 生存概率

为什么要这样:这样就可以简化画表的难度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值