No33 卡牌翻面求和问题
问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ai,背面是 bi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
解题思路
-
这个问题是一个 组合问题,其中的挑战是如何通过选择卡牌的正面或背面,确保最终的数字之和能够被 3 整除。我们可以利用 动态规划 来解决这个问题。
1. 模 3 转换:
由于最终的数字之和需要满足被 3 整除,因此我们只关心数字对 3 的余数(即
mod 3)。对于每张卡牌,它的正面和背面的数字分别为 ai 和bi,我们只需要关注它们对 3 的余数。对于每张卡牌的选择,我们有两种情况:
- 选择正面:选定ai,其对 3 的余数为 ai%3。
- 选择背面:选定 bi,其对 3 的余数为 bi%3。
我们需要将所有这些选择的结果累加并保持对 3 的余数。
2. 动态规划状态设计:
我们定义一个动态规划数组
dp,其中dp[i][j]表示前i张卡牌中,选择某些卡牌使得和模3余j的方案数。初始化时,
dp[0][0] = 1,表示在没有选择任何卡牌时,数字之和的余数为 0,方案数为 1。3. 动态规划转移:
对于每一张卡牌的正面和背面选择,都会更新当前的余数。假设对于当前选择的卡牌,正面数字对 3 的余数为
p[i][(j + num1) % 3],背面数字对 3 的余数为dp[i][(j + num2) % 3]。我们可以按以下规则更新动态规划数组:- 选择正面:如果当前数据是
dp[i - 1][j],选择正面更新后的数据是dp[i][(j + num1) % 3] + dp[i - 1][j]。 - 选择背面:如果当前数据是
dp[i - 1][j],选择背面更新后的数据是dp[i][(j + num2) % 3] + dp[i - 1][j]。
4. 动态规划更新:
遍历每一张卡牌,并根据上述选择规则更新
dp数组。更新时需要注意使用一个临时数组来避免在更新过程中覆盖原来的值。示例代码
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]; // 初始化 dp[0][0] = 1; // 遍历每张卡牌 for (int i = 1; i <= n; i++) { // 当前卡牌的正面和背面的数字 int num1 = a[i - 1]; int num2 = b[i - 1]; // 更新 dp 数组 for (int j = 0; j < 3; j++) { // 选择正面 dp[i][(j + num1) % 3] = (dp[i][(j + num1) % 3] + dp[i - 1][j]) % MOD; // 选择背面 dp[i][(j + num2) % 3] = (dp[i][(j + num2) % 3] + dp[i - 1][j]) % MOD; } } // 最终答案 return dp[n][0]; }