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

80 阅读3分钟

简单记录一下《卡牌方面求和》这道题解题思路。

题面如下:

问题描述

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

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

样例1:

对于n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2],即3张卡牌,正反面数字分别为 (1,2)(2,3) 和 (3,2),考虑的组合可以是1,3,2正面、背面、背面,也可以是1,2,3正面、正面、背面,还可以是2,2,2背面,正面,背面总共3种方案数。

样例2:

对于n = 4 ,a = [3,1, 2, 4] ,b = [1,2, 3, 1],即4张卡牌,正反面数字分别为 (3,1)(1,2)(2,3) 和 (4,1),考虑的组合可以是3,2,3,4正面、背面、背面、正面,也可以是3,2,3,1正面、背面、背面、背面,还可以是1,2,2,4背面、背面、正面、正面,还可以是1,2,2,1背面、背面、正面、背面,还可以是1,1,3,4背面、正面、背面、正面还可以是1,1,3,1背面、正面、背面、背面。总共6种方案数。

解题思路

问题可以转化为,我们需要找到一种计算方法,对于 n 组数字对,每队从中抽选一个数字并计算这n个数字之和并保证可以被3整除,最终统计不同的抽取方案个数。

下面我们先来独立思考一下这道题要怎么做:

1、由于我们需要计算数字之和是否能被3整除,这个问题很容易联想到使用动态规划(DP)来记录当前数字之和模3的不同状态。

2、我们可以使用一个长度为3的数组 dp来存储状态,其中 dp[i] 表示当前数字之和模3为 i 的方案数。

3、对于每一张卡牌,我们有两个选择(正面或背面),也就是一组数字对,因此我们需要分别考虑两种情况来更新 dp 数组。具体来说,对于每张卡牌的正面数字 aia_i 和背面数字 bib_i,我们可以通过以下方式更新 dp 数组:

# 选择正面
new_dp[(j + a[i]) % 3] += dp[j]
# 选择背面
new_dp[(j + b[i]) % 3] += dp[j]

每次更新后,我们需要将 new_dp 赋值给 dp,并继续处理下一张卡牌。

4、考虑到当n的值太大时,方案数量可能过大,我们需要对结果取模。通过这一项计算,最终得到的dp[0]就是我们需要的能被3整除的方案数结果。

完整代码如下:

def solution(n: int, a: list, b: list) -> int:
    MOD = 10 ** 9 + 7
    # 初始化
    dp = [0] * 3
    dp[0] = 1 # 数字之和模3之后为0的方案,初始化为1

    for i in range(n):
        new_dp = [0] * 3
        for j in range(3):
            if dp[j] > 0:
                # 选择正面
                new_dp[(j + a[i]) % 3] = (new_dp[(j + a[i]) % 3] + dp[j]) % MOD
                # print(i, j, new_dp)
                # 选择背面
                new_dp[(j + b[i]) % 3] = (new_dp[(j + b[i]) % 3] + dp[j]) % MOD
                # print(i, j, new_dp)
        dp = new_dp
    
    return dp[0]

可以看到结果是正确的:

image.png