一、问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例1:
输入:
n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]
输出:3
样例2:
输入:
n = 4 ,a = [3, 1, 2, 4] ,b = [1, 2, 3, 1]
输出:6
样例3:
输入:
n = 5 ,a = [1, 2, 3, 4, 5] ,b = [1, 2, 3, 4, 5]
输出:32
二、代码展示
MOD = 10 ** 9 + 7
def solution(n, a, b):
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(3):
dp[i][j] += dp[i - 1][(j - a[i - 1]) % 3]
dp[i][j] += dp[i - 1][(j - b[i - 1]) % 3]
dp[i][j] %= MOD
return dp[n][0]
if __name__ == '__main__':
print(solution(n = 3, a = [1, 2, 3], b = [2, 3, 2]) == 3)
print(solution(n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]) == 6)
print(solution(n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]) == 32)
三、代码解析
1.整体功能概述
这段Python代码定义了一个函数 solution ,其目的是根据给定的输入 n 、列表 a 和列表 b ,通过动态规划的方式计算并返回一个特定的结果。从代码最后的测试部分可以看出,对于不同的 n 、 a 和 b 的取值组合,期望得到相应的计算结果并进行验证。
2.代码细节分析
(1)定义常量 MOD
MOD = 10 ** 9 + 7
这里定义了一个常量 MOD ,它的值为 1000000007 。在后续的计算中,会经常对计算结果取模(即求余数)操作,使用这个常量来确保计算结果在一个特定的范围内,避免整数溢出等问题,这在处理一些可能涉及较大数值的计算场景中是很常见的做法。
(2)函数 solution 的定义
def solution(n, a, b):
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(3):
dp[i][j] += dp[i - 1][(j - a[i - 1]) % 3]
dp[i][j] += dp[i - 1][(j - b[i - 1]) % 3]
dp[i][j] %= MOD
return dp[n][0]
(3)初始化动态规划数组 dp :
- dp = [[0] * 3 for _ in range(n + 1)] :创建了一个二维列表 dp ,它的第一维大小为 n + 1 ,第二维大小为 3 。这个数组将用于存储中间计算结果,通过动态规划的思想逐步构建最终要返回的结果。
- dp[0][0] = 1 :初始化 dp 数组的左上角元素为 1 ,这通常是动态规划问题中的初始状态设定,为后续的递推计算提供了一个起始点。
动态规划递推计算过程:
- 外层循环 for i in range(1, n + 1) :按照从 1 到 n 的顺序逐步遍历,每一次循环都在基于上一步的结果计算当前步的 dp 值,这符合动态规划从子问题逐步构建到整个问题的解决思路。
- 内层循环 for j in range(3) :对于每一个 i 值,都要对 j 从 0 到 2 进行遍历。这里的 j 取值范围为 3 ,可能与问题本身的某种状态划分相关,比如可能代表了三种不同的情况或者状态。
在每次内层循环中:
- dp[i][j] += dp[i - 1][(j - a[i - 1]) % 3] :这一步是根据动态规划的递推关系,将上一步(即 i - 1 时)的某个相关状态的值累加到当前状态 dp[i][j] 中。具体来说,是通过 (j - a[i - 1]) % 3 来索引到上一步的某个特定位置的值,这里取模操作 % 3 可能与前面提到的三种状态相关,确保索引值在 0 到 2 的范围内。
- dp[i][j] += dp[i - 1][(j - b[i - 1]) % 3] :与上一步类似,只不过这里是根据列表 b 中的值来索引上一步的另一个相关位置的值,并累加到当前的 dp[i][j] 。
- dp[i][j] %= MOD :在每次累加完上一步的相关值后,立即对结果进行取模操作,保证 dp[i][j] 的值始终在 0 到 MOD - 1 的范围内,防止数值过大导致溢出等问题。
- 返回最终结果: return dp[n][0] :在完成了所有的动态规划递推计算后,返回 dp 数组中最后一行(即 n 行)第一列(即 0 列)的元素值,这个值就是根据给定的输入 n 、 a 和 b 通过前面的计算过程得到的最终结果。
3.主程序部分
if __name__ == '__main__':
print(solution(n = 3, a = [1, 2, 3], b = [2, 3, 2]) == 3)
print(solution(n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]) == 6)
print(solution(n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]) == 32)
这里是主程序部分,当脚本直接运行(而不是被作为模块导入到其他脚本中)时,会执行这部分代码。它通过调用 solution 函数并传入不同的参数组合 n 、 a 和 b ,然后将函数返回值与预期的结果进行比较,并打印出比较的结果(是否相等)。这样可以方便地对 solution 函数进行测试,验证其对于不同输入是否能正确计算出预期的结果。