怪兽存活概率问题

怪兽存活概率问题

作者:Grey

原文地址:

博客园:怪兽存活概率问题

CSDN:怪兽存活概率问题

题目描述

给定3个参数,N,M,K 怪兽有 N 滴血,等着英雄来砍自己,英雄每一次打击,都会让怪兽流失,
怪兽每一次流失的血量在区间[0……M]上等概率的获得一个值,求 K 次打击之后,英雄把怪兽砍死的概率。

主要思路

由题目含义可知:怪兽在经历 K 次打击后所有可能的掉血情况有 (M+1) 的 K 次方种,,即:

long all = (long) Math.pow(M + 1, K)

如果怪兽在 K 次打击后,被砍死的情况有 kill 种,那么

(double) kill / (double) all;

即为怪兽被砍死的概率。

暴力解法

定义递归函数

long process(int times, int M, int hp)

递归含义是:怪兽还剩 hp 点血,每次的伤害在[0……M]范围上,还有 times 次可以砍,返回砍死的情况数。

那么 base case 有如下两种情况

// 情况一:已经没有被砍的次数了,这个时候,血量如果正好是小于等于0的值, 说明怪兽已经被砍死一次
// 否则怪兽不可被砍死
if (times == 0) {
    return hp <= 0 ? 1 : 0;
}
// 情况二:怪兽已经死了,但是还可以砍
// 此时,所有的砍法都满足条件,所以情况就是(long) Math.pow(M + 1, times)
if (hp <= 0) {
    return (long) Math.pow(M + 1, times);
}

接下来就是普遍情况,由于每次攻击是 [0……M] 中等概率的一个值,则枚举从 0 到 M 任意一个值跑递归函数即可。

long ways = 0;
for (int i = 0; i <= M; i++) {
    ways += process(times - 1, M, hp - i);
}

完整代码如下

public class Code_KillMonster {
    public static double right(int N, int M, int K) {
        if (N < 1 || M < 1 || K < 1) {
            return 0;
        }
        // monster在经历K次打击后所有可能的掉血情况是
        long all = (long) Math.pow(M + 1, K);
        long kill = process(K, M, N);
        return (double) kill / (double) all;
    }

    //怪兽还剩 hp 点血,每次的伤害在[0……M]范围上,还有 times 次可以砍,返回砍死的情况数。
    public static long process(int times, int M, int hp) {
        // 情况一:已经没有被砍的次数了,这个时候,血量如果正好是小于等于0的值, 说明怪兽已经被砍死一次
        // 否则怪兽不可被砍死
        if (times == 0) {
            return hp <= 0 ? 1 : 0;
        }
        // 情况二:怪兽已经死了,但是还可以砍
        // 此时,所有的砍法都满足条件,所以情况就是(long) Math.pow(M + 1, times)
        if (hp <= 0) {
            return (long) Math.pow(M + 1, times);
        }
        long ways = 0;
        for (int i = 0; i <= M; i++) {
            ways += process(times - 1, M, hp - i);
        }
        return ways;
    }
}

动态规划(未做枚举优化)

根据上述暴力递归函数可以得知,递归函数的可变参数有两个,分别是 times 和 hp,且变化范围是固定的,可以定义一个二维数组 dp,表示所有的递归过程解

long[][] dp = new long[K + 1][N + 1];

dp[times][hp] 就表示递归函数long process(int times, int M, int hp)的含义,即:怪兽还剩 hp 点血,每次的伤害在[0……M]范围上,还有 times 次可以砍,砍死的情况数有多少。

根据 base case, 可知

dp[0][0] = 1;

dp[times][0] = (long) Math.pow(M + 1, times)

接下来就是普遍位置,根据上述暴力递归函数可知:process(times, M, hp)依赖process(times - 1, M, hp - i)

dp[times][hp]依赖dp[times-1][hp-i]位置,如下图所示

image

图中绿色部分的格子依赖黄色部分的格子,

代码如下,

for (int times = 1; times <= K; times++) {
    dp[times][0] = (long) Math.pow(M + 1, times);
    for (int hp = 1; hp <= N; hp++) {
        long ways = 0;
        for (int i = 0; i <= M; i++) {
            if (hp - i >= 0) {
                ways += dp[times - 1][hp - i];
            } else {
                ways += (long) Math.pow(M + 1, times - 1);
            }
        }
        dp[times][hp] = ways;
    }
}

完整代码如下

public class Code_KillMonster {
    public static double dp1(int N, int M, int K) {
        if (N < 1 || M < 1 || K < 1) {
            return 0;
        }
        long all = (long) Math.pow(M + 1, K);
        long[][] dp = new long[K + 1][N + 1];
        dp[0][0] = 1;
        for (int times = 1; times <= K; times++) {
            dp[times][0] = (long) Math.pow(M + 1, times);
            for (int hp = 1; hp <= N; hp++) {
                long ways = 0;
                for (int i = 0; i <= M; i++) {
                    if (hp - i >= 0) {
                        ways += dp[times - 1][hp - i];
                    } else {
                        ways += (long) Math.pow(M + 1, times - 1);
                    }
                }
                dp[times][hp] = ways;
            }
        }
        long kill = dp[K][N];
        return (double) ((double) kill / (double) all);
    }
}

动态规划(枚举优化)

上述动态规划解法中的第三个循环可以优化,再一次看下依赖关系图

image

当我们得到绿色格子,即dp[times][hp]位置的值以后,如果要求dp[times+1][hp]位置的时候,即如下 target 位置

image

可以考虑 G 和 H 两个位置

image

因为 G 位置求的时候,紫色部分格子已经求过了,补上一个 H 位置,就可以把 target 求出来,省略了枚举行为。

完整代码如下

public class Code_KillMonster {
    public static double dp2(int N, int M, int K) {
        if (N < 1 || M < 1 || K < 1) {
            return 0;
        }
        long all = (long) Math.pow(M + 1, K);
        long[][] dp = new long[K + 1][N + 1];
        dp[0][0] = 1;
        for (int times = 1; times <= K; times++) {
            dp[times][0] = (long) Math.pow(M + 1, times);
            for (int hp = 1; hp <= N; hp++) {
                dp[times][hp] = dp[times][hp - 1] + dp[times - 1][hp];
                if (hp - 1 - M >= 0) {
                    dp[times][hp] -= dp[times - 1][hp - 1 - M];
                } else {
                    dp[times][hp] -= Math.pow(M + 1, times - 1);
                }
            }
        }
        long kill = dp[K][N];
        return (double) ((double) kill / (double) all);
    }
}

更多

算法和数据结构笔记

### 回答1: Wumpus怪兽世界是经典的人工智能问题之一,主要涉及到基于逻辑推理的搜索算法。这个问题描述了一个由方格组成的地下迷宫,在这个迷宫中有一个凶猛的wumpus怪兽,它随机地在地图中移动。此外,地图中还有一些悬崖和障碍物,玩家需要避开这些危险来到wumpus怪兽的所在位置并消灭它。 为了解决这个问题,需要使用C语言编写相关的算法代码,利用逻辑推理和搜索算法来完成任务。具体来说,需要使用谓词逻辑语言描述地图中的对象、条件和动作,通过搜索算法在可能的行动空间中进行探索,不断地更新代表当前状态的数据结构,计算每个操作的收益,并选择最优操作。 在编写代码的过程中,可以使用基本的数据结构算法库,例如链表、堆栈、队列、哈希表等。同时,需要注意处理异常情况,如遇到无可行路径、假设的位置错误等等。好的算法设计和实现可以在保证代码结构清晰和高效的同时,提高解决问题的准确性。在编写代码后,可以利用测试用例来验证算法的正确性和性能。 ### 回答2: Wumpus是一个基于人工智能的小游戏,原始版是由Gregory Yob在20世纪70年代初开发的。该游戏最初是在DEC PDP-10计算机上开发的,后来被移植到了其他平台上。 Wumpus怪兽世界的目标是在地图上搜寻一个金箭头,然而这个任务并不容易。地图中隐藏的不仅仅是金箭头,还有一只喜欢吃肉的怪兽,以及一些随机放置的陷阱。你需要小心翼翼地在地图中移动,同时避开怪兽和陷阱,寻找金箭头并将其带回起点。 在C语言中实现Wumpus怪兽世界,需要维护一个游戏地图的二维数组。数组中使用特定的数字表示不同的元素,比如0表示空地,1表示怪兽,2表示陷阱,3表示金箭头等。同时,需要实现各种函数来计算和判断游戏中的状态,比如确认玩家是否移动到了有怪兽或陷阱的地方、是否找到了金箭头等等。 对于玩家来说,掌握游戏规则和避开陷阱和怪兽,探索地图变得更加容易。同时,开发者也需要耐心跟踪和调试代码,以确保程序的正确性和运行效率,从而实现一个高质量的Wumpus怪兽世界游戏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GreyZeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值