问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
题目解析
题目要求我们计算小M有多少种不同的方案可以选择卡牌的正反面,使得所有向上的数字之和可以被3整除。这是一个典型的动态规划问题,我们可以通过维护一个状态数组来记录到达每个状态的可能性。
思路
-
状态定义:定义一个二维数组
dp[i][j],其中i表示考虑前i张卡牌,j表示当前所有卡牌向上的数字之和对3取模的结果。dp[i][j]的值表示到达这个状态的可能性数量。 -
状态转移方程:
- 对于每张卡牌,有两种选择:选择正面或背面。
- 如果选择正面,则当前状态
dp[i][j]转移到dp[i+1][(j + a[i-1]) % 3]。 - 如果选择背面,则当前状态
dp[i][j]转移到dp[i+1][(j + b[i-1]) % 3]。
-
初始化:
dp[0][0] = 1,表示没有卡牌时,和为0的可能性为1。 -
结果:最终结果为
dp[n][0],表示使用所有卡牌后,和能被3整除的可能性数量。
图解
dp[i][j] 表示考虑前 i 张卡牌,和为 j 的可能性数量
dp[0][0] = 1
dp[1][0] = dp[0][0] + dp[0][1] + dp[0][2] (选择 a[0] 或 b[0])
dp[2][0] = dp[1][0] + dp[1][1] + dp[1][2] (选择 a[1] 或 b[1])
...
dp[n][0] = dp[n-1][0] + dp[n-1][1] + dp[n-1][2] (选择 a[n-1] 或 b[n-1])
代码详解
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
# 初始化 dp 数组
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
# 遍历每张卡牌
for i in range(1, n + 1):
for j in range(3):
# 选择正面
dp[i][(j + a[i - 1]) % 3] = (dp[i][(j + a[i - 1]) % 3] + dp[i - 1][j]) % MOD
# 选择背面
dp[i][(j + b[i - 1]) % 3] = (dp[i][(j + b[i - 1]) % 3] + dp[i - 1][j]) % MOD
# 返回和能被3整除的可能性数量
return dp[n][0]
知识总结
-
动态规划:动态规划是一种解决序列问题的方法,通过维护一个状态数组来记录到达每个状态的可能性。
-
取模运算:在计算过程中,为了避免整数溢出,可以使用取模运算。
-
状态转移方程:根据问题的定义,推导出状态转移方程,是解决动态规划问题的关键。
学习建议
-
理解动态规划的基本概念:动态规划是一种解决序列问题的方法,通过维护一个状态数组来记录到达每个状态的可能性。
-
掌握状态转移方程的推导方法:状态转移方程是解决动态规划问题的关键,需要根据问题的定义进行推导。
-
练习编程实现:通过编程实现动态规划问题,加深对动态规划的理解。