卡牌正反面数字和的整除方案数求解 | 豆包MarsCode AI刷题

32 阅读4分钟

卡牌正反面数字和的整除方案数求解

问题描述

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

示例1:

输入:n = 3a = [1, 2, 3]b = [2, 3, 2]

输出:3

示例2:

输入:n = 4a = [3, 1, 2, 4]b = [1, 2, 3, 1]

输出:6

示例3:

输入:n = 5a = [1, 2, 3, 4, 5]b = [1, 2, 3, 4, 5]

输出:32

问题分析

思路概述

本题要求计算满足特定条件的方案总数,即选择每张卡牌的正面或背面,使得所有向上的数字之和能被 33 整除。由于每张卡牌都有两个选择(正面或背面),并且我们需要考虑总和的模 33 值,这提示我们可以使用动态规划来解决这个问题。

状态定义

dp[s] 表示当前已处理的卡牌,其向上数字之和模 33ss 的方案数。由于模 33 的结果只有 0,1,20, 1, 2 三种可能,所以 dp 数组的大小为 33

转移方程

对于每张卡牌,我们有两个选择:

  1. 选择正面数字 aia_i
  2. 选择背面数字 bib_i

我们需要更新 dp,使其包含当前卡牌的选择结果。对于每个可能的前一状态 s,我们可以通过选择当前卡牌的正面或背面,计算新的模 33 值,并累加方案数。

aibia_i \ne b_i 时:

for s in range(3):
    for num in [a_i, b_i]:
        new_mod = (s + num) % 3
        temp_dp[new_mod] = (temp_dp[new_mod] + dp[s]) % MOD

ai=bia_i = b_i 时:

即使正反面的数字相同,但由于卡牌的两面不同,我们仍然有两种不同的选择(正面朝上或反面朝上)。因此,方案数需要乘以 22

for s in range(3):
    new_mod = (s + a_i) % 3
    temp_dp[new_mod] = (temp_dp[new_mod] + dp[s] * 2) % MOD

初始条件

最初,没有选任何卡牌时,和为 00,因此 dp[0] = 1

代码详解

def solution(n: int, a: list, b: list) -> int:
    MOD = 10**9 + 7  # 模数
    dp = [0] * 3     # dp[s] 表示和模 3 为 s 的方案数
    dp[0] = 1        # 初始条件,和为 0 的方案数为 1
    for i in range(n):
        temp_dp = [0] * 3  # 用于更新 dp 的临时数组
        if a[i] == b[i]:
            # 当正反面数字相同,但选择不同,方案数要乘以 2
            for s in range(3):
                new_mod = (s + a[i]) % 3
                temp_dp[new_mod] = (temp_dp[new_mod] + dp[s] * 2) % MOD
        else:
            # 正反面数字不同,分别考虑选择 a[i] 和 b[i]
            for s in range(3):
                for num in [a[i], b[i]]:
                    new_mod = (s + num) % 3
                    temp_dp[new_mod] = (temp_dp[new_mod] + dp[s]) % MOD
        dp = temp_dp  # 更新 dp
    return dp[0] % MOD  # 返回和能被 3 整除的方案数

代码解释

  1. 初始化: dp[0] = 1,表示初始状态下,和为 00 的方案数为 11

  2. 遍历每张卡牌:

    • 创建一个临时数组 temp_dp,用于存储更新后的方案数。

    • ai=bia_i = b_i 时:

      • 对于每个可能的前一状态 s,计算新的模 33new_mod,方案数累加 dp[s] * 2(因为有两种选择,但数字相同)。
    • aibia_i \ne b_i 时:

      • 对于每个可能的前一状态 s,分别考虑选择 a_ib_i,计算新的模 33 值,方案数累加 dp[s]
  3. 更新 dptemp_dp 的值赋给 dp,进入下一次迭代。

  4. 结果返回: 最终 dp[0] 即为满足条件的方案数。

测试样例验证

示例1:

输入:n = 3a = [1, 2, 3]b = [2, 3, 2]

通过上述算法,计算得出方案数为 3,与示例输出一致。

示例2:

输入:n = 4a = [3, 1, 2, 4]b = [1, 2, 3, 1]

计算得出方案数为 6,与示例输出一致。

示例3:

输入:n = 5a = [1, 2, 3, 4, 5]b = [1, 2, 3, 4, 5]

计算得出方案数为 32,与示例输出一致。

复杂度分析

  • 时间复杂度: O(n)O(n),其中 nn 为卡牌数量。因为我们需要遍历每张卡牌,对于每张卡牌的操作都是常数级别。
  • 空间复杂度: O(1)O(1)dp 数组的大小固定为 33,与 nn 无关。

总结

本题通过将问题转化为模 33 的动态规划,巧妙地解决了组合数求解的问题。在处理相同数字的卡牌时,注意到虽然数字相同,但选择的面不同,仍然算作不同的方案,需要倍乘。这一细节是解题的关键所在。

通过本题,我们学习了如何将求组合数的问题转化为状态转移问题,以及在动态规划中处理模运算的技巧。这对于解决类似的组合计数问题具有重要的参考价值。