问题描述
小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
解题思路 —— 动态规划
二、解题思路——动态规划“闪亮登场”
动态规划的核心在于“以小见大、递推求解”。在此题里,关键是定义合适的状态。我们设dp[i][j]表示前i张卡牌,其数字总和除以3余数为j的方案数。这么定义状态,就像搭建乐高积木,从最基础的“0 张卡牌”情况逐步往上垒,去推算更多卡牌时的方案。
初始状态很明晰,当没有卡牌(i = 0)时,只有一种情况能让余数为0,就是啥都不选呀,所以dp[0][0] = 1,而dp[0][1] = dp[0][2] = 0。
接着进入状态转移环节。对于第i张卡牌(i > 0),有选正面a_i或者选背面b_i两种操作。要是选正面,它对应的数字a_i加入已选数字总和里,会改变余数情况。比如当前已有的前(i - 1)张卡牌组合余数是(j),选了正面后新余数就是(j + a_i % 3) % 3,那对应方案数就得累加到dp[i][(j + a_i % 3) % 3]上;选背面(b_i)同理,把相应方案数累加到对应余数位置。每步累加后都对10^9 + 7取模,稳稳控制数值规模。
三、实现代码(Java 示例)
public class Main {
private static final int MOD = 1000000007;
public static int solution(int n, int[] a, int[] b) {
int[][] dp = new int[n + 1][3];
// 初始化边界
dp[0][0] = 1;
for (int i = 0; i < n; i++) {
int num1 = a[i];
for (int j = 0; j < 3; j++) {
dp[i + 1][(j + num1 % 3) % 3] += dp[i][j];
dp[i + 1][(j + num1 % 3) % 3] %= MOD;
}
int num2 = b[i];
for (int j = 0; j < 3; j++) {
dp[i + 1][(j + num2 % 3) % 3] += dp[i][j];
dp[i + 1][(j + num2 % 3) % 3] %= MOD;
}
}
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);
}
}
在代码里,先定义常量用于取模操作。solution方法里,二维数组dp承载各状态方案数,按上述思路双层循环,外层遍历卡牌,内层针对余数操作,不断更新方案数。main方法则是简单验证几个测试样例,看输出是否符合预期,确保代码正确性。
总之,利用动态规划把复杂的卡牌组合计数难题拆解为有序步骤,从初始设定到逐步递推,最终拿到满足条件的方案总数,这种方法在很多类似组合优化问题里都大有用武之地,希望大家也能灵活运用!