卡牌翻面求和问题

102 阅读3分钟

问题描述

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

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

题目解析

题目要求我们计算小M有多少种不同的方案可以选择卡牌的正反面,使得所有向上的数字之和可以被3整除。这是一个典型的动态规划问题,我们可以通过维护一个状态数组来记录到达每个状态的可能性。

思路

  1. 状态定义:定义一个二维数组 dp[i][j],其中 i 表示考虑前 i 张卡牌,j 表示当前所有卡牌向上的数字之和对3取模的结果。dp[i][j] 的值表示到达这个状态的可能性数量。

  2. 状态转移方程

    • 对于每张卡牌,有两种选择:选择正面或背面。
    • 如果选择正面,则当前状态 dp[i][j] 转移到 dp[i+1][(j + a[i-1]) % 3]
    • 如果选择背面,则当前状态 dp[i][j] 转移到 dp[i+1][(j + b[i-1]) % 3]
  3. 初始化dp[0][0] = 1,表示没有卡牌时,和为0的可能性为1。

  4. 结果:最终结果为 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]

知识总结

  1. 动态规划:动态规划是一种解决序列问题的方法,通过维护一个状态数组来记录到达每个状态的可能性。

  2. 取模运算:在计算过程中,为了避免整数溢出,可以使用取模运算。

  3. 状态转移方程:根据问题的定义,推导出状态转移方程,是解决动态规划问题的关键。

学习建议

  1. 理解动态规划的基本概念:动态规划是一种解决序列问题的方法,通过维护一个状态数组来记录到达每个状态的可能性。

  2. 掌握状态转移方程的推导方法:状态转移方程是解决动态规划问题的关键,需要根据问题的定义进行推导。

  3. 练习编程实现:通过编程实现动态规划问题,加深对动态规划的理解。