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

49 阅读4分钟

问题描述

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

输入输出示例

示例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整除的方案数量。


解题思路

这道题的核心问题是:如何通过选择卡牌的一面,使得所有朝上的数字之和被3整除?

由于我们要确定被3整除的条件,这意味着我们只需关心每张卡牌数字对3取模的结果。具体来说:

  • 每张卡牌的正面或背面数字可能对3取余结果是0、1或2。
  • 因此,我们可以使用模3的余数来更新方案数。

步骤1:模3分类

对于每张卡牌,我们可以分别计算它的正反面数字模3后的结果。这样,我们只需要记录和追踪这些模3余数的组合数即可。

步骤2:动态规划

我们使用动态规划的方法来求解这个问题。通过动态规划数组 dp[i][j] 表示前 i 张卡牌中选择若干张卡牌的一面,使得这些数字之和对3取余数等于 j 的方案数。

其中 j 的取值范围是0到2(因为我们只关心是否能被3整除),具体定义为:

  • dp[i][0] 表示前 i 张卡牌中可以得到的所有和中被3整除的方案数。
  • dp[i][1] 表示前 i 张卡牌中可以得到的所有和中对3余1的方案数。
  • dp[i][2] 表示前 i 张卡牌中可以得到的所有和中对3余2的方案数。

步骤3:转移方程

对于每张卡牌的正面数字 a_i 和背面数字 b_i,我们可以基于前一张卡牌的结果来更新当前卡牌的方案数:

  • 如果选择正面 a_i,则会将 dp[i-1][k] 更新到 dp[i][(k + a_i) % 3]
  • 如果选择背面 b_i,则会将 dp[i-1][k] 更新到 dp[i][(k + b_i) % 3]

我们对每一张卡牌进行上述更新,直到遍历完所有卡牌。

步骤4:初始化

对于 dp[0][0],我们初始化为1,因为在没有选择任何卡牌的情况下,我们的和为0,并且0可以被3整除,这是一种有效方案。其他状态初始化为0。

步骤5:取模

为了避免方案数过大导致的溢出问题,每次更新 dp 时都需要对结果取模 (10^9+7)。

步骤6:返回结果

最后,我们需要的结果是 dp[n][0],即从所有卡牌中选择若干张,使得和能够被3整除的方案数。


Python代码实现

以下是根据上述思路实现的Python代码:

MOD = 10**9 + 7

def solution(n: int, a: list, b: list) -> int:
    # dp[i][j] 表示前 i 张卡牌中,选择若干张后数字和模 3 等于 j 的方案数
    dp = [[0] * 3 for _ in range(n + 1)]
    dp[0][0] = 1  # 初始化

    for i in range(1, n + 1):
        # 当前卡牌的正反面数字对3取模后的值
        mod_a = a[i - 1] % 3
        mod_b = b[i - 1] % 3
        
        # 临时存储 dp[i-1] 的拷贝,以便更新 dp[i] 时不覆盖
        new_dp = [0] * 3
        for j in range(3):
            # 选择 a_i
            new_dp[(j + mod_a) % 3] = (new_dp[(j + mod_a) % 3] + dp[i - 1][j]) % MOD
            # 选择 b_i
            new_dp[(j + mod_b) % 3] = (new_dp[(j + mod_b) % 3] + dp[i - 1][j]) % MOD
        
        # 更新 dp[i]
        dp[i] = new_dp

    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)

复杂度分析

  • 时间复杂度:(O(n)),因为我们只需遍历一次卡牌数组,每张卡牌进行3次余数状态的更新。
  • 空间复杂度:(O(3n)),因为我们只存储每张卡牌的模3状态。

总结

希望本博客能帮助你更好地理解动态规划,解决更多组合类问题!