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

44 阅读3分钟

问题描述

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

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

分析

对于这个问题,我们可以使用动态规划的方法来解决:

我们可以定义一个三维数组 dp[i][j][k],其中 i 表示前 i 张卡牌,j 表示当前所有选择数字之和模3的结果,k 表示当前选择的数字个数。

对于每张卡牌,我们可以选择正面或背面,这会影响当前的总和以及选择的数字个数。我们需要根据卡牌的正反两面数字来更新 dp 数组。

dp[0][0][0] = 1,表示没有选择任何卡牌时,总和为0,有1种方案。

最终我们需要计算的是 dp[n][0][n],即考虑所有卡牌后,数字之和模3为0,且选择了所有卡牌的方案数。

MOD = 10**9 + 7
def solution(n: int, a: list, b: list) -> int:
    # write code here
    # 初始化动态规划数组
    dp = [[[0 for _ in range(n+1)] for _ in range(3)] for _ in range(n+1)]
    dp[0][0][0] = 1  # 初始状态,没有选择任何卡牌,和为0

    for i in range(1, n+1):
        for j in range(3):
            for k in range(i):
                # 不选择当前卡牌的正面
                dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k]) % MOD
                # 选择当前卡牌的正面
                new_j = (j + a[i-1] % 3) % 3
                dp[i][new_j][k+1] = (dp[i][new_j][k+1] + dp[i-1][j][k]) % MOD

                # 选择当前卡牌的背面
                new_j = (j + b[i-1] % 3) % 3
                dp[i][new_j][k+1] = (dp[i][new_j][k+1] + dp[i-1][j][k]) % MOD

    return dp[n][0][n]
  • 遍历卡牌:外层循环 for i in range(1, n+1) 遍历每张卡牌。
  • 遍历和模3的结果:中层循环 for j in range(3) 遍历所有可能的和模3的结果(0, 1, 2)。
  • 遍历选择的数字个数:内层循环 for k in range(i) 遍历所有可能的选择数字个数。注 意,这里不包括 i,因为我们至少要选择一张卡牌的一面。
  • 不选择当前卡牌的正面:dp[i][j][k] 的值增加 dp[i-1][j][k],表示不选择当前卡牌的正面,保持当前和模3的结果和选择的数字个数不变。
  • 选择当前卡牌的正面:计算新的和模3的结果 new_j,并更新 dp[i][new_j][k+1],表示选择当前卡牌的正面,更新和模3的结果,并增加选择的数字个数。
  • 选择当前卡牌的背面:同样计算新的和模3的结果 new_j,并更新 dp[i][new_j][k+1],表示选择当前卡牌的背面,更新和模3的结果,并增加选择的数字个数。
  • 返回结果:最后返回 dp[n][0][n],这表示考虑所有 n 张卡牌,和为0(能被3整除)

总结、比较

我们使用了动态规划,这与题库里“小E的怪物挑战”这道题的基本思路一致,最大区别是维度不同。通过两次相似的训练,我们可以熟悉动态规划的用途、用法,灵活解决问题。