问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例1:
输入:
n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]
输出:3
样例2:
输入:
n = 4 ,a = [3, 1, 2, 4] ,b = [1, 2, 3, 1]
输出:6
样例3:
输入:
n = 5 ,a = [1, 2, 3, 4, 5] ,b = [1, 2, 3, 4, 5]
输出:32
思路
这道题可以视为对 0/1 背包问题的变形,不过0/1 背包问题通常是求最大价值,而卡牌翻面问题是求所有组合数。为了解决这个问题,我们可以使用动态规划来跟踪不同选择下的状态。
1. 状态定义
我们定义一个二维数组 dp,其中 dp[i][j] 表示前 i 张卡牌中,选择的数字和模3为 j 的组合数。这里 j 的取值为 0、1、2,分别表示当前和对3的余数。
2. 初始化
dp[0][0] = 1,表示不选择任何卡牌时,数字和为 0,这显然满足模3为0的条件。
3. 状态转移
对于每张卡牌,我们有两个选择:
- 选择正面 a[i−1]a[i-1]a[i−1]:则新的模3值为
(j + a[i-1]) % 3。 - 选择背面 b[i−1]b[i-1]b[i−1]:则新的模3值为
(j + b[i-1]) % 3。
在计算过程中,我们需要遍历当前状态 j 的所有可能情况,并根据选择的面更新 dp 状态。这样,在每次选择卡牌后,我们都会形成新的状态,并将结果累加到 dp 数组中。
4. 结果计算
最终,我们需要的结果就是所有选择后和模3为0的组合数,显然对应的就是 dp[n][0]。
示例演示
例如:假设我们有三张卡牌,卡牌的数字如下:
- a=[1,2,3]a = [1, 2, 3]a=[1,2,3]
- b=[2,3,2]b = [2, 3, 2]b=[2,3,2]
其选择过程如下所示:
步骤1 初始化
dp[0] = [1, 0, 0] // 不选择任何卡牌,和为 0
步骤 2:选择第一张卡牌
选择正面 1:
dp[1][(0 + 1) % 3] += dp[0][0] => dp[1][1] += 1
选择背面 2:
dp[1][(0 + 2) % 3] += dp[0][0] => dp[1][2] += 1
现在 dp[1] = [1, 1, 1] // 和为 0 或 1 或 2
步骤 3:选择第二张卡牌
选择正面 2:
dp[2][(0 + 2) % 3] += dp[1][0] => dp[2][2] += 1
dp[2][(1 + 2) % 3] += dp[1][1] => dp[2][0] += 1
dp[2][(2 + 2) % 3] += dp[1][2] => dp[2][1] += 1
选择背面 3:
dp[2][(0 + 3) % 3] += dp[1][0] => dp[2][0] += 1
dp[2][(1 + 3) % 3] += dp[1][1] => dp[2][1] += 1
dp[2][(2 + 3) % 3] += dp[1][2] => dp[2][2] += 1
现在 dp[2] = [2, 2, 2]
步骤 4:选择第三张卡牌
以此类推,继续更新状态直到处理完所有卡牌。
最终,得到 dp[n][0],它的值就是满足条件的方案数。
以下是Python代码实现:
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
# dp[i][j]表示前i张卡牌,选择的数字和模3为j的组合数
dp = [[0] * 3 for _ in range(n + 1)]
# 初始化
dp[0][0] = 1 # 不选择任何卡牌,和为0
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为0的方案数
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)
总结
解决此问题的关键在于利用动态规划的思想来系统地追踪和的模 3 状态。核心在于将问题分解为较小的子问题,通过构建状态转移方程,将每张卡牌的选择与当前状态关联起来,从而逐步计算出所有可能的组合。
首先,明确状态定义非常重要。我们使用 dp[i][j] 来表示前 i 张卡牌中选择的数字和模 3 等于 j 的组合数。这一结构化的表示使得我们可以清晰地看到每个状态之间的关系。其次,状态转移方程的构造是动态规划的关键。我们通过选择每张卡牌的正面或背面,计算出每种选择对当前模 3 状态的影响,确保所有可能的组合都被考虑到。