找到特定结果的 n 间隔动作的组合

Finding the combinations of n-spaced actions to a specific result

给定以下语句:

2 名玩家各有 100HP

他们都可以使用 -2HP、-5HP 或 -7HP 进行攻击

他们可以连续攻击无限次

HP <= 0 玩家输

我需要找出玩家 A 战胜玩家 B 的情况的数量。

到目前为止,这是我的代码:

import itertools

win = 0
for j in itertools.product(range(1, 7), repeat=100):
    a = 100
    b = 100
    for i in j:
        if (i == 1):
            b -= 2
        elif (i == 2):
            b -= 5
        elif (i == 3):
            b -= 7
        elif (i == 4):
            a -= 2
        elif (i == 5):
            a -= 5
        elif (i == 6):
            a -= 7
        if b <= 0 and a > 0:
            win += 1
            if (win % 1000000 == 0):
                print(j)
                print(win)
            break
        if a <= 0:
            break
   

对于极端情况,玩家 A 和 B 都只用 2HP 攻击命中,我选择了 100 次重复。

当然,这会花费大量时间来计算。

我需要找到一个更快的算法。

首先使用 DP 填充矩阵 s.t。位置 (i,j) 的值表示在 j 步中失去 i 健康的方式的计数

arr[i,j] = arr[i-2, j-1] + arr[i-5, j-1] + arr[i-7, j-2],只考虑先验非获胜值(因此我在先前值中 < 100)。

初始化arr[0,0] = 1

我们将按照 j 然后 i 的顺序填充它,从 j=1 开始

对于 i >= 100,索引 (i, j) 处的值表示在 j 步中获得获胜分数的方式的计数。这将上升到 106,这是最大获胜分数 (99+7)。类似地,对于 i < 100,这些表示在 j 步中获得失败分数的方法的计数。

对于每一步数,计算在该步数中获胜和失败的方式数。我们将能够在 0-49 步内输,并能够在 15-50 步内获胜。

现在,对于每一个获胜的分数,对于每一个失败的分数,我们需要计算这些计数可以交错的方式。假设我们在 j 步中获胜,在 k 步中失败。这是 choose(j + k - 1, k).

-1 是因为最后一步(必胜棋)必须来自必胜棋盘。将此视为从 j+k-1 种可能性中选择 k 种失败走法的位置,以涵盖以获胜走法结束的每一种排列。

最终答案:对于每一种在 j 步中获胜并在 k 步中失败的方法,我们添加 (j-move wins) * (count of k-move loss) * choose(j+k-1, k) 到我们的总数。

例如最大 hp 为 10 的矩阵和 wins/losses 小计:

> count_wins([2,5,7], 10)
hp loss: 0      1|   0|   0|   0|   0|   0|
hp loss: 1      0|   0|   0|   0|   0|   0|
hp loss: 2      0|   1|   0|   0|   0|   0|
hp loss: 3      0|   0|   0|   0|   0|   0|
hp loss: 4      0|   0|   1|   0|   0|   0|
hp loss: 5      0|   1|   0|   0|   0|   0|
hp loss: 6      0|   0|   0|   1|   0|   0|
hp loss: 7      0|   1|   2|   0|   0|   0|
hp loss: 8      0|   0|   0|   0|   1|   0|
hp loss: 9      0|   0|   2|   3|   0|   0|
hp loss: 10     0|   0|   1|   0|   0|   1|
hp loss: 11     0|   0|   0|   3|   4|   0|
hp loss: 12     0|   0|   2|   2|   0|   0|
hp loss: 13     0|   0|   0|   0|   1|   1|
hp loss: 14     0|   0|   1|   4|   3|   0|
hp loss: 15     0|   0|   0|   0|   0|   1|
hp loss: 16     0|   0|   0|   2|   3|   0|
loss:           1|   3|   5|   4|   1|   0|
win:            0|   0|   4|  11|  11|   3|
=> 4078 (ways of winning)

对于 3 的最大 hp,我得到:

count_wins([2,5,7], 3)
loss:           1|   1|   0|
win:            0|   2|   3|
=> 13 (ways of winning)

A获胜的13种方式。它们是:

A's moves: [[5], [7], [2,2], [2,5], [2,7]]
B's moves: [[], [2]]

A 获胜的 5 种方式中的每一种都可以是没有 B 步 (5),有一个初始 B 步 (5),或者在这 3 种情况下在两个 A 步之间有一个 B 步适用 (3)。根据上述算法,我们的总数是 13。

对于 count_wins([2,5,7], 100) 我得到的最终答案是:

102,033,940,458,046,779,283,026,415,918,520,992,505,663

对于任何dp问题,通常最简单的方法是将解决方案写成递归函数然后缓存。

这是解决方案

def ways_for_a_to_win (a_hp, b_hp, moves=None, cache=None):
    if moves is None:
        moves = [2, 5, 7]
    if cache is None:
        cache = {}

    if a_hp <= 0:
        return 0
    elif b_hp <= 0:
        return 1
    else:
        if (a_hp, b_hp) not in cache:
            answer = 0
            for move in moves:
                answer += ways_for_a_to_win(a_hp - move, b_hp, moves, cache)
                answer += ways_for_a_to_win(a_hp, b_hp - move, moves, cache)
            cache[(a_hp, b_hp)] = answer
        return cache[(a_hp, b_hp)]

print(ways_for_a_to_win(100, 100))