问题解析****
· 我们有 n 张卡牌,每张卡牌有正面数字(存储在列表 a 中)和背面数字(存储在列表 b 中)。
· 目标是找出通过选择每张卡牌的一面(正面或背面),使得所有向上的数字之和能被 3 整除的不同方案数量。
· 由于可能的方案数量非常大,所以最终结果要对 10^9 + 7 取模,以避免整数溢出等问题并得到一个合适范围内的结果。
解决步骤****
1. 状态定义:
· 我们可以使用动态规划来解决这个问题。定义一个三维数组 dp,其中 dp[i][j][k] 表示考虑前 i 张卡牌,当前数字之和对 3 取模的结果为 j,且第 i 张卡牌选择的是第 k 面(k = 0 表示正面,k = 1 表示背面)的方案数量。
2. 初始化:
· 当没有卡牌时(i = 0),只有一种情况,即数字之和为 0,且没有选择卡牌,所以 dp[0][0][0] = dp[0][0][1] = 1。这是因为不管是从正面还是背面开始考虑(虽然实际上还没选卡牌),和为 0 的情况有一种方案(就是什么都没选的初始状态)。
3. 状态转移:
· 对于每张卡牌 i(从 1 到 n),我们要根据前 i - 1 张卡牌的状态来更新当前状态。
· 当考虑选择第 i 张卡牌的正面(k = 0)时:
· 对于和对 3 取模的每一种可能结果 j(j = 0, 1, 2),我们可以从 dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][0] 和 dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][1] 转移过来。这里 (j - a[i - 1] % 3 + 3) % 3 是为了处理负数取模的情况,确保得到正确的前序状态索引。
· 即 dp[i][j][0] = (dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][0] + dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][1]) % MOD,其中 MOD = 10**9 + 7。
· 当考虑选择第 i 张卡牌的背面(k = 1)时:
· 同样对于和对 3 取模的每一种可能结果 j(j = 0, 1, 2),我们从 dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][0] 和 dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][1] 转移过来。
· 即 dp[i][j][1] = (dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][0] + dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][1]) % MOD。
4. 最终结果:
· 考虑完所有 n 张卡牌后,满足条件的方案总数就是 dp[n][0][0] + dp[n][0][1],因为我们要的是所有数字之和能被 3 整除的情况(对 3 取模结果为 0),并且最后一张卡牌可以选择正面或背面。
代码实现****
MOD = 10**9 + 7
def solution(n: int, a: list, b: list) -> int:
dp = [[0, 0, 0] for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
prev_dp = dp[i - 1][:]
for j in range(3):
new_remainder = (j + a[i - 1]) % 3
dp[i][new_remainder] = (dp[i][new_remainder] + prev_dp[j]) % MOD
new_remainder = (j + b[i - 1]) % 3
dp[i][new_remainder] = (dp[i][new_remainder] + prev_dp[j]) % MOD
return dp[n][0]
if name == 'main':
print(solution(n = 3, a = [1, 2, 3], b = [2, 3, 2]))
print(solution(n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]))
print(solution(n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]))
在上述代码中:
· 首先定义了常量 MOD 为 10**9 + 7,用于取模操作。
· 然后创建了三维数组 dp 并进行初始化。
· 通过两层循环,分别遍历每张卡牌和数字和对 3 取模的各种情况,根据状态转移方程更新 dp 数组。
· 最后返回满足所有数字之和能被 3 整除的方案总数,即 dp[n][0][0] + dp[n][0][1] 对 MOD 取模的结果。