卡牌翻面求和问题题解分析(动态规划) | 豆包MarsCode AI刷题

48 阅读5分钟

一、题目背景

小M有 𝑛 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 𝑎𝑖,背面是 𝑏𝑖。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 10^9+7 取模。

二、解决思路

通过动态规划来解决。我们可以使用一个 dp 数组,dp[i] 代表当前的和对 3 取模的结果。我们需要依次考虑每张卡牌的选择,更新 dp 数组的状态。

  • 动态规划状态定义

我们可以定义 dp[j] 为当前已选择的卡牌数字和对 3 取模为 j 的方案数。即,dp[0] 表示和对 3 取模为 0 的方案数,dp[1] 表示和对 3 取模为 1 的方案数,dp[2] 表示和对 3 取模为 2 的方案数。

  • 状态转移

对于每张卡牌 i,我们有两个选择:

  1. 选择卡牌的正面 a[i],这会改变当前和对 3 取模的值。
  2. 选择卡牌的背面 b[i],这也会改变当前和对 3 取模的值。

假设当前我们已经计算出了前 i-1 张卡牌的方案数,并且我们已经知道 dp 数组的状态,那么对于卡牌 i,我们可以更新新的状态 dp'(存储当前卡牌 i 之后的状态):

  • 对于每个可能的当前状态 j(即 dp[j]),我们有两种选择:

    1. 如果选择正面 a[i],则新的状态是 (j + a[i]) % 3
    2. 如果选择背面 b[i],则新的状态是 (j + b[i]) % 3

三、代码实现与解析

def solution(n: int, a: list, b: list) -> int:
    MOD = 10**9 + 7  # 模数
    dp = [0] * 3  # dp数组,用于记录和对3取模的方案数
    dp[0] = 1  # 和为0的方案数初始化为1

    for i in range(n):
        new_dp = [0] * 3  # 临时新dp数组,保存更新后的方案数
        for j in range(3):
            if dp[j] > 0:
                # 选择正面
                new_dp[(j + a[i]) % 3] = (new_dp[(j + a[i]) % 3] + dp[j]) % MOD
                # 选择背面
                new_dp[(j + b[i]) % 3] = (new_dp[(j + b[i]) % 3] + dp[j]) % MOD
        dp = new_dp  # 更新dp为新的dp数组

    return dp[0]  # 最终结果是和为0的方案数,即dp[0]

# 测试用例
if __name__ == '__main__':
    print(solution(n = 3, a = [1, 2, 3], b = [2, 3, 2]))  # 输出3
    print(solution(n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]))  # 输出6
    print(solution(n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]))  # 输出32

代码解析

  1. 初始化 dp 数组
    dp 数组的大小为 3,因为我们只关心和对 3 取模的结果。dp[0] 表示当前和对 3 取模为 0 的方案数,dp[1] 表示当前和对 3 取模为 1 的方案数,dp[2] 表示当前和对 3 取模为 2 的方案数。我们初始化 dp[0] = 1,表示没有卡牌时,和为 0 的方案数是 1(空集合)。

  2. 遍历每张卡牌
    对于每一张卡牌,我们需要根据选择正面或背面来更新 dp 数组:

    对于当前的每个 dp[j](表示当前和对 3 取模为 j 的方案数),如果我们选择卡牌的正面,新的和为 (j + a[i]) % 3,如果选择背面,新的和为 (j + b[i]) % 3

  3. 状态转移
    我们使用一个新的 new_dp 数组来存储更新后的状态。更新过程是累加的,因为每个状态 j 可以通过两种选择(正面或背面)得到两个新的状态。

  4. 最终结果
    经过所有卡牌的处理后,dp[0] 就表示所有选择中和对 3 取模为 0 的方案数。这个值就是最终的结果。

  5. 取模
    每次更新 dp 数组时,都要对 109+7109+7 取模,以防止数值过大。

四、复杂度分析

  • 时间复杂度
    我们对每张卡牌都遍历一次,每次更新 dp 数组的大小为 3。因此,总的时间复杂度为 O(n),其中 n 是卡牌的数量。
  • 空间复杂度
    我们使用了一个大小为 3 的数组 dp 来存储每个和对 3 取模的方案数。因此空间复杂度为 O(3),即常数空间。

五、总结

通过动态规划,我们有效地解决了这个问题。使用一个大小为 3 的数组 dp 来记录每一步和对 3 取模的不同方案数,并通过状态转移实现从一个卡牌到下一个卡牌的过渡。最终,我们得到了所有和对 3 取模为 0 的方案数。

在之前,我并不能很好地理解动态规划的题目,只是大概了解了状态转移的概念。尤其是在书写转移方程时,我常常会出错,导致无法顺利解决问题。然而,在这次通过AI刷题的过程中,我逐步理解了动态规划的核心思想,特别是在AI的帮助下,我一步步跟着分析问题,理清了状态转移的过程。通过不断练习,我逐渐能够准确地编写转移方程,并最终完整地解决问题。