伴学笔记|青训营X豆包MarsCode 技术训练营

90 阅读4分钟

选题:### 问题描述

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

例如:如果有3张卡牌,正反面数字分别为 (1,2)(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。 问题理解:好的,让我们详细解释一下这个问题的核心要求。

问题核心

我们需要找到一种方法,使得从 n 张卡牌中选择正面或背面朝上,使得所有选中的数字之和可以被3整除。由于可能的方案数量很大,结果需要对 10^9+7 取模。

详细解释

  1. 卡牌选择

    • 每张卡牌有两个面,正面和背面,分别写着不同的数字。
    • 你需要从每张卡牌中选择一个面(正面或背面),使得所有选中的数字之和可以被3整除。
  2. 数字之和

    • 假设你选择了第 i 张卡牌的正面数字 a_i,或者背面数字 b_i
    • 你需要确保所有选中的数字之和 sum(selected_numbers) 可以被3整除,即 sum(selected_numbers) % 3 == 0
  3. 方案数量

    • 由于可能的方案数量很大,结果需要对 10^9+7 取模。
    • 这意味着你需要计算所有可能的方案数,并将结果对 10^9+7 取模,以避免整数溢出。

示例解释

让我们通过一个简单的例子来进一步理解:

假设有3张卡牌,正反面数字分别为 (1,2)(2,3)(3,2)

  • 选择 1, 2, 3:和为 1 + 2 + 3 = 6,可以被3整除。
  • 选择 1, 3, 2:和为 1 + 3 + 2 = 6,可以被3整除。
  • 选择 2, 2, 3:和为 2 + 2 + 3 = 7,不能被3整除。
  • 选择 2, 3, 2:和为 2 + 3 + 2 = 7,不能被3整除。
  • 选择 2, 2, 3:和为 2 + 2 + 3 = 7,不能被3整除。
  • 选择 2, 3, 2:和为 2 + 3 + 2 = 7,不能被3整除。

在这个例子中,只有两种选择(1, 2, 31, 3, 2)满足条件,因此结果为2。

总结

我们需要找到所有可能的组合,使得所有选中的数字之和可以被3整除,并将结果对 10^9+7 取模。

数据结构选择

由于我们需要计算所有可能的组合,并且需要考虑每张卡牌的正面和背面,因此可以使用递归或动态规划来解决这个问题。

算法步骤

  1. 递归思路

    • 定义一个递归函数 count_ways(index, remainder),其中 index 表示当前处理的卡牌索引,remainder 表示当前选择的数字之和对3的余数。
    • 如果 index 等于 n,表示已经处理完所有卡牌,此时如果 remainder 为0,则表示找到了一种有效方案,返回1,否则返回0。
    • 否则,递归调用 count_ways 处理下一张卡牌,分别考虑当前卡牌的正面和背面,并更新 remainder
    • 最终返回所有有效方案的数量,并对 10^9+7 取模。
  2. 动态规划思路

    • 定义一个二维数组 dp[index][remainder],其中 index 表示当前处理的卡牌索引,remainder 表示当前选择的数字之和对3的余数。
    • dp[index][remainder] 表示从第 index 张卡牌开始,使得数字之和对3的余数为 remainder 的方案数。
    • 初始化 dp[n][0] = 1,表示当处理完所有卡牌时,余数为0的方案数为1。
    • 从后向前填充 dp 数组,根据当前卡牌的正面和背面更新 dp 值。
    • 最终返回 dp[0][0],表示从第一张卡牌开始,使得数字之和对3的余数为0的方案数。

代码框架

def solution(n, a, b):

    def count_ways

    (index, remainder)

    :

        # 如果已经处理完

        所有卡牌

        if index == n:

            return 1 

            if 

            remainder 

            == 0 else 

            0

        

        # 递归处理下一张

        卡牌,分别考虑当

        前卡牌的正面和背

        面

        ways = 0

        ways += 

        count_ways

        (index + 1, 

        (remainder + a

        [index]) % 3)

        ways += 

        count_ways

        (index + 1, 

        (remainder + b

        [index]) % 3)

        

        # 返回结果并对 

        10^9+7 取模

        return ways % 

        (10**9 + 7)

    # 从第一张卡牌开始,

    初始余数为0

    return count_ways

    (0, 0) 感悟:

  • 递归方法可能会导致重复计算,可以使用记忆化搜索(memoization)来优化。
  • 动态规划方法可以避免重复计算,并且通常比递归方法更高效。