卡片翻面求和问题 | 豆包MarsCode AI刷题

79 阅读4分钟

问题描述

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

解题思路

  1. 我们可以将每张卡牌的正面和背面的数字分别对3取余,得到它们的余数。这样,每张卡牌的正面和背面的余数只有三种可能:0、1、2。将问题“使得所有向上的数字之和可以被3整除”转化为“余数和为0”。

  2. 我们可以使用动态规划来解决这个问题。设 dp[i][j] 表示前 i 张卡牌中,选择某些面使得总和的余数为 j 的方案数。其中 j 的取值范围是 0, 1, 2

  3. 状态转移

    对于第 i 张卡牌,我们可以选择正面或背面。

    如果选择正面,那么新的余数为 (j + ai % 3) % 3

    如果选择背面,那么新的余数为 (j + bi % 3) % 3

    因此,状态转移方程为:

dp[i][(j + ai % 3) % 3] += dp[i-1][j]
dp[i][(j + bi % 3) % 3] += dp[i-1][j]
  1. 初始状态为dp[0][0] = 1,表示前0张卡牌,总和为0的方案数为1。

  2. 最终状态为dp[n][0] 即为所求的方案数。

代码详解

def solution(n: int, a: list, b: list) -> int:
    # write code here
    MOD = 10**9 + 7
    
    # 初始化dp数组
    dp = [[0] * 3 for _ in range(n + 1)]
    dp[0][0] = 1  # 初始状态,前0张卡牌,总和为0的方案数为1
    
    for i in range(1, n + 1):
        ai_mod = a[i-1] % 3
        bi_mod = b[i-1] % 3
        
        for j in range(3):
            dp[i][(j + ai_mod) % 3] = (dp[i][(j + ai_mod) % 3] + dp[i-1][j]) % MOD
            dp[i][(j + bi_mod) % 3] = (dp[i][(j + bi_mod) % 3] + dp[i-1][j]) % MOD
    
    return dp[n][0]
    pass

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)

要注意的是动态规划的核心部分:

for j in range(3):
    dp[i][(j + ai_mod) % 3] = (dp[i][(j + ai_mod) % 3] + dp[i-1][j]) % MOD
    dp[i][(j + bi_mod) % 3] = (dp[i][(j + bi_mod) % 3] + dp[i-1][j]) % MOD

for环遍历所有可能的余数状态 j, j = {0, 1, 2}

dp[i][(j + ai_mod) % 3] = (dp[i][(j + ai_mod) % 3] + dp[i-1][j]) % MOD处理的是选择第 i 张卡牌的正面 ai 的情况: dp[i][(j + ai_mod) % 3] 表示在前 i 张卡牌中,选择某些面使得总和的余数为 (j + ai_mod) % 3 的方案数,dp[i-1][j] 表示在前 i-1 张卡牌中,选择某些面使得总和的余数为 j 的方案数,两者相加即为第i张卡牌是正面时前i张卡牌余数为j的方案数。

同理,dp[i][(j + bi_mod) % 3] = (dp[i][(j + bi_mod) % 3] + dp[i-1][j]) % MOD处理的是选择第 i 张卡牌的正面 bi 的情况:最后得到第i张卡牌是正面时前i张卡牌余数为j的方案数。

总结

上述解法应用了动态规划思想:

  • 将复杂问题分解为更小的子问题。
  • 找到状态之间的关系,即如何从一个状态转移到另一个状态。
  • 确定最简单的子问题的解。
  • 根据状态转移方程和初始条件,计算出最终问题的解。

在上述问题中我们使用模运算来简化问题。通过将每张卡牌的正面和背面的数字对3取余,我们可以将问题转化为处理余数的问题。这有助于减少计算的复杂度。在状态规划的核心部分即状态转移部分,遍历所有可能的余数状态 j,并根据第 i 张卡牌的正面和背面选择,更新所有可能的新余数状态,并对结果取模,以避免溢出。

通过解决上述问题,我学会了动态规划的基本概念和步骤,还掌握了模运算的应用、动态规划的实现技巧,有助于进一步学习和应用更复杂的算法问题。