题目
小M有 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ,背面是 。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 取模。 例如:如果有3张卡牌,正反面数字分别为(1,2),(2,3)和(3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
关键点
- 卡牌选择:每张卡牌有两个选择:正面或背面。
- 和的整除性:所有选择的数字之和必须可以被3整除。
- 取模操作:由于可能的方案数量过大,结果需要对
10^9 + 7取模。
解题思路
-
动态规划:
- 使用动态规划来记录每一步的状态。
dp[i][j]表示前i张卡牌中,选择某些卡牌使得它们的和模3余j的方案数。
-
状态转移:
- 对于每一张卡牌
i,我们有两个选择:选择正面a[i-1]或背面b[i-1]。 - 对于每种可能的余数
j,我们更新dp[i][(j + front) % 3]和dp[i][(j + back) % 3]。
- 对于每一张卡牌
-
初始化:
dp[0][0] = 1:表示前0张卡牌,和为0的方案数为1。
-
最终结果:
- 返回
dp[n][0],即前n张卡牌中,和模3余0的方案数。
- 返回
数据结构选择
- 使用一个二维数组
dp[i][j]来表示前i张卡牌中,选择某些卡牌使得它们的和模3余j的方案数。
算法步骤
-
初始化:
dp[0][0] = 1。
-
状态转移:
- 对于每一张卡牌
i,更新dp数组。
- 对于每一张卡牌
-
最终结果:
- 返回
dp[n][0]。
- 返回
代码
public class Main {
public static int solution(int n, int[] a, int[] b) {
final int MOD = 1000000007;
// dp[i][j] 表示前 i 张卡牌中,选择某些卡牌使得它们的和模3余 j 的方案数
int[][] dp = new int[n + 1][3];
// 初始化:前0张卡牌,和为0的方案数为1
dp[0][0] = 1;
// 遍历每一张卡牌
for (int i = 1; i <= n; i++) {
// 当前卡牌的正面和背面数字
int front = a[i - 1];
int back = b[i - 1];
// 更新 dp 数组
for (int j = 0; j < 3; j++) {
// 选择正面的情况
dp[i][(j + front) % 3] = (dp[i][(j + front) % 3] + dp[i - 1][j]) % MOD;
// 选择背面的情况
dp[i][(j + back) % 3] = (dp[i][(j + back) % 3] + dp[i - 1][j]) % MOD;
}
}
// 返回前 n 张卡牌中,和模3余0的方案数
return dp[n][0];
}
public static void main(String[] args) {
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{2, 3, 2}) == 3);
System.out.println(solution(4, new int[]{3, 1, 2, 4}, new int[]{1, 2, 3, 1}) == 6);
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 32);
}
}
详细解释
- 常量定义:
final int MOD = 1000000007;
- `MOD` 是一个常量,用于对结果取模,防止溢出。
- 动态规划数组初始化:
int[][] dp = new int[n + 1][3];
dp[0][0] = 1;
- `dp[i][j]` 表示前 `i` 张卡牌中,选择某些卡牌使得它们的和模3余 `j` 的方案数。
- `dp[0][0] = 1` 表示前0张卡牌,和为0的方案数为1。
- 遍历每一张卡牌:
for (int i = 1; i <= n; i++) {
int front = a[i - 1];
int back = b[i - 1];
- 从第1张卡牌开始遍历到第 `n` 张卡牌。
- `front` 和 `back` 分别表示当前卡牌的正面和背面数字。
- 更新 dp 数组:
for (int j = 0; j < 3; j++) {
dp[i][(j + front) % 3] = (dp[i][(j +
front) % 3] + dp[i - 1][j]) % MOD;
dp[i][(j + back) % 3] = (dp[i][(j +
back) % 3] + dp[i - 1][j]) % MOD;
}
- 对于每种可能的余数 `j`,我们更新 `dp[i][(j + front) % 3]` 和 `dp[i][(j + back) % 3]`。
- `(j + front) % 3` 和 `(j + back) % 3` 分别表示选择正面或背面后,新的余数。
- 取模操作 `% MOD` 确保结果不会溢出。
-
返回结果:
- 返回
dp[n][0],即前n张卡牌中,和模3余0的方案数。
- 返回
-
测试用例:
public static void main(String[] args) {
System.out.println(solution(3, new int
[]{1, 2, 3}, new int[]{2, 3, 2}) ==
3);
System.out.println(solution(4, new int
[]{3, 1, 2, 4}, new int[]{1, 2, 3,
1}) == 6);
System.out.println(solution(5, new int
[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3,
4, 5}) == 32);
}
- 在 `main` 函数中,提供了一些测试用例来验证代码的正确性。