题目反思与总结笔记:卡牌翻面求和问题
题目概述
在这个问题中,小M有 n 张卡牌,每张卡牌的正面和背面上分别写着不同的数字。目标是选择每张卡牌的一面,使得所有卡牌上数字的总和能够被 3 整除。需要计算满足此条件的所有选择方案的数量,并对结果取模 109+710^9 + 7109+7。
解题思路分析
-
问题的本质:
- 每张卡牌可以选择正面或背面,因而每个选择会对总和产生不同的影响。
- 通过动态规划(DP)来记录每种选择方案下的和对 3 的余数。
-
状态定义:
- 定义
dp[i][j]表示前i张卡牌中,选择后所有数字和对 3 取余为j的方案数。 j可以取值 0、1、2,分别表示和对 3 的余数。
- 定义
-
初始状态:
dp[0][0] = 1:没有卡牌时,和为 0 的方案只有 1 种(即不选任何卡牌)。- 其他状态
dp[0][1]和dp[0][2]初始化为 0。
-
状态转移:
-
对于第
i张卡牌,有两种选择:- 选择正面 aia_iai:更新当前和的余数。
- 选择背面 bib_ibi:同样更新当前和的余数。
-
状态转移公式:
-
dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]%3+3)%3])mod (109+7)dp[i][j] = (dp[i-1][(j - a[i-1] % 3 + 3) % 3] + dp[i-1][(j - b[i-1] % 3 + 3) % 3]) \mod (10^9 + 7)dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]%3+3)%3])mod(109+
dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]) %3+3)%3])mod(107)
- 通过对每张卡牌的选择,更新所有可能的余数状态。
-
最终结果:
- 需要返回的结果是
dp[n][0],即所有卡牌选择后,和对 3 取余为 0 的方案数。
- 需要返回的结果是
复杂度分析
- 时间复杂度:O(n)
每张卡牌遍历 3 种余数的状态转移,整体复杂度为 O(n)。 - 空间复杂度:O(n)
只需要一个大小为 (n+1)×3(n + 1) \times 3(n+1)×3 的二维数组来保存状态。
代码实现
以下是完整的 Python 实现代码:
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1 # 初始状态,没有卡牌时和为0的方案数为1
for i in range(1, n + 1):
for j in range(3):
dp[i][j] = (dp[i-1][(j - a[i-1] % 3 + 3) % 3] + dp[i-1][(j - b[i-1] % 3 + 3) % 3]) % MOD
return dp[n][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)
总结
通过动态规划的方法,我们有效地解决了卡牌翻面求和的问题。关键在于正确建立状态并进行转移。在处理此类问题时,关注状态的定义和转移公式是求解的核心。