问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ai,背面是 bi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 10^9+7 取模。
解题思路
-
我们可以将每张卡牌的正面和背面的数字分别对3取余,得到它们的余数。这样,每张卡牌的正面和背面的余数只有三种可能:0、1、2。将问题“使得所有向上的数字之和可以被3整除”转化为“余数和为0”。
-
我们可以使用动态规划来解决这个问题。设
dp[i][j]表示前i张卡牌中,选择某些面使得总和的余数为j的方案数。其中j的取值范围是0, 1, 2。 -
状态转移
对于第
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]
-
初始状态为
dp[0][0] = 1,表示前0张卡牌,总和为0的方案数为1。 -
最终状态为
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 张卡牌的正面和背面选择,更新所有可能的新余数状态,并对结果取模,以避免溢出。
通过解决上述问题,我学会了动态规划的基本概念和步骤,还掌握了模运算的应用、动态规划的实现技巧,有助于进一步学习和应用更复杂的算法问题。