简单记录一下《卡牌方面求和》这道题解题思路。
题面如下:
问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ,背面是 。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 取模。
例如:如果有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 数组。具体来说,对于每张卡牌的正面数字 和背面数字 ,我们可以通过以下方式更新 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]
可以看到结果是正确的: