1.题目
问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ai,背面是 bi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 10^9+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
2.思路
动态规划
- 状态定义
dp[i][j]
表示前 i
张卡牌中,选择某些卡牌使得它们的数字和模 3 等于 j
的方案数。
-
状态转移: 对于第
i
张卡牌,其正反面数字为a[i-1]
和b[i-1]
:- 如果选择正面,数字之和模 3 的更新公式为:
dp[i][(j+a[i−1])%3]+=dp[i−1][j]
- 如果选择背面,数字之和模 3 的更新公式为:
dp[i][(j+b[i−1])%3]+=dp[i−1][j]
- 如果选择正面,数字之和模 3 的更新公式为:
dp[i-1][j]
:前 i-1
张卡牌中余数为 j
的方案数。假设取正面,余数变为(j+a[i−1])%3
状态 (j + a[i]) % 3
和 (j + b[i]) % 3
都可能被多个路径触及。为了记录所有可能路径的方案数,我们需要对它们进行累加,故使用 +=
。
计算时,需要对 10^9 + 7
取模。
dp[i][(j + a[i]) % 3] = (dp[i][(j + a[i]) % 3] + dp[i-1][j]) % MOD;
dp[i][(j + b[i]) % 3] = (dp[i][(j + b[i]) % 3] + dp[i-1][j]) % MOD;
初始状态:
dp[0][0] = 1
,表示前 0 张卡牌和为 0 的方案数为 1。- 其他状态
dp[0][1] = dp[0][2] = 0
。
结果计算:
dp[n][0]
即为答案。
示例:
假设有 2 张卡牌,正反面数字为:
- 卡牌 1:
a[1] = 1
,b[1] = 2
- 卡牌 2:
a[2] = 3
,b[2] = 2
初始状态:
dp[0][0] = 1 (没有选卡牌,余数为 0)
dp[0][1] = dp[0][2] = 0
处理第 1 张卡牌:
选择 a[1] = 1:
dp[1][(0 + 1) % 3] = dp[1][1] + dp[0][0] = 0 + 1 = 1
选择 b[1] = 2:
dp[1][(0 + 2) % 3] = dp[1][2] + dp[0][0] = 0 + 1 = 1
结果:
dp[1][0] = 0
dp[1][1] = 1
dp[1][2] = 1
3.代码
我的代码
public class Main {
public static int solution(int n, int[] a, int[] b) {
// write code here
final int MOD = 1000000007;
int[][] dp = new int[n + 1][3];
dp[0][0] = 1;
//第1张牌到第n张牌
for (int i = 1; i <= n; i++){
//正面
int front = a[i-1];
//反面
int back = b[i-1];
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;
}
}
return dp[n][0]; // Placeholder return
}
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);
}
}
参考代码
public static int solution(int n, int[] a, int[] b)
{
final int MOD = 1000000007;
int[][] dp = new int[n + 1][3];
// 初始化
dp[0][0] = 1; // 前0张卡牌和为0的方案数为1
// 动态规划
for (int i = 1; i <= n; i++)
{
int front = a[i - 1]; // 当前卡牌正面数字
int back = b[i - 1]; // 当前卡牌背面数字
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;
}
}
return dp[n][0];
}
4.参考资料
卡牌翻面求和问题 | 豆包MarsCode AI刷题问题描述 小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字 a - 掘金