卡牌翻面求和问题
难度:难
问题描述
小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
问题分析
这个问题是一个典型的动态规划问题,我们的目标是通过选择每张卡牌的正面或背面,使得所有选中的卡牌上的数字之和能够被 3 整除。
解决思路
-
状态定义:
设dp[r]表示当前选中的卡牌数字和对 3 取余等于r的方案数,其中r可能是 0, 1 或 2。即:dp[0]:数字和除以 3 的余数为 0 的方案数。dp[1]:数字和除以 3 的余数为 1 的方案数。dp[2]:数字和除以 3 的余数为 2 的方案数。
-
初始状态:
最初没有选择任何卡牌,所以只有一种方案(即空集),即dp[0] = 1(和为 0),而dp[1] = 0和dp[2] = 0。 -
状态转移:
对于每一张卡牌,我们有两种选择:- 选择卡牌的正面
a[i],这样我们会从当前余数r转移到新的余数(r + a[i]) % 3。 - 选择卡牌的背面
b[i],这样我们会从当前余数r转移到新的余数(r + b[i]) % 3。
对于每张卡牌,我们会更新
dp数组,使得新的状态能够反映当前选卡牌的正面或背面。 - 选择卡牌的正面
-
状态更新:
每次更新时,我们必须注意更新顺序。为了避免在同一轮计算中使用未更新的状态,应该先记录当前的状态(通过dp数组),然后使用new_dp数组来存储更新后的结果。 -
最终结果:
最终,我们关心的是dp[0],即所有选中的卡牌上的数字和能够被 3 整除的方案数。
MOD = 10**9 + 7
def solution(n: int, a: list, b: list) -> int:
# dp[r] 表示当前和为 r % 3 的方案数
dp = [0] * 3
dp[0] = 1 # 初始时和为 0 的方案数为 1
for i in range(n):
# 新的 dp 数组,用来存储当前轮次的更新结果
new_dp = [0] * 3
for r in range(3):
new_dp[(r + a[i]) % 3] = (new_dp[(r + a[i]) % 3] + dp[r]) % MOD
new_dp[(r + b[i]) % 3] = (new_dp[(r + b[i]) % 3] + dp[r]) % MOD
dp = new_dp
# 最终我们需要 dp[0] 的值,即总和可以被 3 整除的方案数
return dp[0]
时间复杂度
- 遍历每张卡牌一次,且每张卡牌需要遍历 3 种余数。因此时间复杂度是 O(n * 3) = O(n),其中 n 是卡牌的数量。
空间复杂度
- 由于我们只需要一个大小为 3 的
dp数组,所以空间复杂度为 O(3) = O(1),即常数空间。