问题描述
小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
问题理解
你需要找到一种方法,使得从给定的卡牌中选择正面或背面朝上的数字,使得这些数字之和可以被3整除。由于卡牌的数量可能很大,直接枚举所有可能的组合是不现实的,因此我们需要一种更高效的方法。
数据结构选择
我们可以使用动态规划(Dynamic Programming, DP)来解决这个问题。动态规划是一种通过将问题分解为子问题并存储子问题的解来避免重复计算的方法。
算法步骤
-
定义状态:
- 我们可以使用一个数组
dp,其中dp[i]表示当前和模3等于i的方案数。
- 我们可以使用一个数组
-
初始化:
- 初始时,没有任何卡牌被选择,和为0,因此
dp[0] = 1,表示和为0的方案数为1。
- 初始时,没有任何卡牌被选择,和为0,因此
-
状态转移:
- 对于每一张卡牌,我们有两个选择:选择正面或选择背面。
- 对于当前的
dp数组,我们需要更新它以反映选择当前卡牌正面或背面后的新状态。 - 具体来说,对于当前的
dp[j],我们可以选择正面a[i]或背面b[i],并更新新的dp数组。
-
结果:
- 最终,
dp[0]将包含所有满足和为3的倍数的方案数。
- 最终,
关键点
- 模运算:由于我们只关心和是否能被3整除,因此我们可以在每一步都使用模3运算来简化计算。
- 取模:由于结果可能非常大,我们需要在每一步都对结果取模
10^9 + 7。
实现代码
public class Main {
public static int solution(int n, int[] a, int[] b) {
int MOD = 1000000007; // 取模值
int[] dp = new int[3]; // dp数组用于存储当前和的模3值的组合数
// 初始化
dp[0] = 1; // 不选择任何卡牌的方案
// 遍历每张卡牌
for (int i = 0; i < n; i++) {
int[] newDp = new int[3]; // 新的dp数组
// 更新方案数
for (int j = 0; j < 3; j++) {
if (dp[j] > 0) {
// 选择正面a[i]
newDp[(j + a[i]) % 3] = (newDp[(j + a[i]) % 3] + dp[j]) % MOD;
// 选择背面b[i]
newDp[(j + b[i]) % 3] = (newDp[(j + b[i]) % 3] + dp[j]) % MOD;
}
}
dp = newDp; // 更新dp为新的方案
}
return dp[0]; // 返回满足和为3的倍数的方案数
}
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);
}
}