卡牌翻面求和问题 | 豆包MarsCode AI 刷题

224 阅读4分钟

卡牌翻面求和问题练习笔记

问题描述

小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai​,背面是 bibi​。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+710^9+7 取模。

例如:如果有3张卡牌,正反面数字分别为 (1,2)(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。

问题分析

我一开始看到这个描述还没搞明白要干啥,但是看完例子就大概明白了题目的意思。小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字。小M希望选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。而我们的任务是计算满足该条件的不同组合数,并输出结果对 (10910^9 + 7) 取模后的值。

示例

假设有3张卡牌:

  • 卡牌1的正反面数字为 (1, 2)
  • 卡牌2的正反面数字为 (2, 3)
  • 卡牌3的正反面数字为 (3, 2)

我们需要找到使得正反面组合后的数字之和可以被3整除的组合数。

题目等级

难点:这道卡牌翻面求和问题属于中等偏难的题目,主要考察动态规划取模运算的技巧。由于需要根据余数进行状态转移和更新,对于初学者来说理解余数的状态管理可能有一定的难度,但掌握后可以显著提升处理类似组合计数问题的能力。

解题思路

这道题的关键在于动态规划。我们可以用状态转移来计算每张卡牌正反面选择后的可能组合情况。

  1. 动态规划数组定义

    • 使用一个长度为3的数组 dp 来表示余数的状态。dp[i] 表示当前选择中,使得数字和对3取余为 i 的组合数。
    • 初始时,dp[0] = 1 表示和为0时有一种方式(即不选任何卡牌时的组合)。
  2. 状态转移

    • 对于每张卡牌,我们可以选择其正面或反面。根据当前余数,加上正面或反面的值后,再对3取余,更新新的组合数。
    • 对于每张卡牌的正反面值,分别取模3来获取它们的余数 rem_arem_b
    • 遍历当前的 dp 数组,根据每种余数状态,更新选择该卡牌正反面后的新状态,并累加组合数。
  3. 更新过程

    • 为了避免在同一轮更新过程中相互干扰,我们可以使用一个 current_dp 数组来存储当前卡牌计算后的新状态。完成当前卡牌的计算后,再将 current_dp 更新回 dp
  4. 取模操作

    • 为避免组合数过大,每次累加时都对 (10^9 + 7) 取模。
  5. 最终结果

    • 经过所有卡牌的遍历后,dp[0] 即为和能被3整除的组合数。

代码实现

以下是完整的代码实现:

def solution(n: int, a: list, b: list) -> int:
    MOD = 10**9 + 7
    
    # 初始化 dp 数组
    dp = [0, 0, 0]
    dp[0] = 1  # 初始状态为和为0的组合数为1
    
    for i in range(n):
        # 临时数组,用于保存当前卡牌的状态更新
        current_dp = [0, 0, 0]
        
        # 计算正反面的模3余数
        rem_a = a[i] % 3
        rem_b = b[i] % 3
        
        for j in range(3):
            # 更新正面朝上的情况
            current_dp[(j + rem_a) % 3] = (current_dp[(j + rem_a) % 3] + dp[j]) % MOD
            # 更新反面朝上的情况
            current_dp[(j + rem_b) % 3] = (current_dp[(j + rem_b) % 3] + dp[j]) % MOD
        
        # 将更新后的 current_dp 赋值回 dp
        dp = current_dp
    
    # 最终结果为和能被3整除的组合数
    return dp[0]

测试

通过几个测试用例来验证代码的正确性:

# 测试
print(solution(3, [1, 2, 3], [2, 3, 2]))  # 输出: 3
print(solution(4, [3, 1, 2, 4], [1, 2, 3, 1]))  # 输出: 6
print(solution(5, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]))  # 输出: 32
  • 样例 1n = 3, a = [1, 2, 3], b = [2, 3, 2]

    • 满足条件的组合有3种,输出为 3
  • 样例 2n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]

    • 满足条件的组合有6种,输出为 6
  • 样例 3n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]

    • 满足条件的组合有32种,输出为 32

小结

这道题使用动态规划的技巧,通过余数状态的转移,巧妙地避免了暴力枚举所有组合情况。将每张卡牌选择的正反面结果存储并更新在 dp 数组中,极大地减少了时间复杂度。最终解得的结果有效地使用了取模运算,确保数值在范围内。

这种余数状态转移的思想可以广泛应用于其他类似问题,是一种重要的动态规划技巧。