卡牌翻面求和问题 | 豆包MarsCode AI刷题

73 阅读4分钟

题目

小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aia_i,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+710^9+7 取模。 例如:如果有3张卡牌,正反面数字分别为(1,2),(2,3)和(3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。

关键点

  1. 卡牌选择:每张卡牌有两个选择:正面或背面。
  2. 和的整除性:所有选择的数字之和必须可以被3整除。
  3. 取模操作:由于可能的方案数量过大,结果需要对 10^9 + 7 取模。

解题思路

  1. 动态规划

    • 使用动态规划来记录每一步的状态。
    • dp[i][j] 表示前 i 张卡牌中,选择某些卡牌使得它们的和模3余 j 的方案数。
  2. 状态转移

    • 对于每一张卡牌 i,我们有两个选择:选择正面 a[i-1] 或背面 b[i-1]
    • 对于每种可能的余数 j,我们更新 dp[i][(j + front) % 3] 和 dp[i][(j + back) % 3]
  3. 初始化

    • dp[0][0] = 1:表示前0张卡牌,和为0的方案数为1。
  4. 最终结果

    • 返回 dp[n][0],即前 n 张卡牌中,和模3余0的方案数。

数据结构选择

  • 使用一个二维数组 dp[i][j] 来表示前 i 张卡牌中,选择某些卡牌使得它们的和模3余 j 的方案数。

算法步骤

  1. 初始化

    • dp[0][0] = 1
  2. 状态转移

    • 对于每一张卡牌 i,更新 dp 数组。
  3. 最终结果

    • 返回 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);
    }
    }

详细解释

  1. 常量定义
final int MOD = 1000000007;
-   `MOD` 是一个常量,用于对结果取模,防止溢出。
  1. 动态规划数组初始化
 int[][] dp = new int[n + 1][3];

    dp[0][0] = 1;
-   `dp[i][j]` 表示前 `i` 张卡牌中,选择某些卡牌使得它们的和模3余 `j` 的方案数。
-   `dp[0][0] = 1` 表示前0张卡牌,和为0的方案数为1。
  1. 遍历每一张卡牌
  for (int i = 1; i <= n; i++) {

        int front = a[i - 1];

        int back = b[i - 1];
-   从第1张卡牌开始遍历到第 `n` 张卡牌。
-   `front` 和 `back` 分别表示当前卡牌的正面和背面数字。
  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;

    }
-   对于每种可能的余数 `j`,我们更新 `dp[i][(j + front) % 3]` 和 `dp[i][(j + back) % 3]`-   `(j + front) % 3` 和 `(j + back) % 3` 分别表示选择正面或背面后,新的余数。
-   取模操作 `% MOD` 确保结果不会溢出。
  1. 返回结果

    • 返回 dp[n][0],即前 n 张卡牌中,和模3余0的方案数。
  2. 测试用例

public static void main(String[] args) {

        System.out.println(solution(3new int

        []{123}, new int[]{232}) == 

        3);

        System.out.println(solution(4new int

        []{3124}, new int[]{123, 

        1}) == 6);

        System.out.println(solution(5new int

        []{12345}, new int[]{123, 

        45}) == 32);

    }
-   在 `main` 函数中,提供了一些测试用例来验证代码的正确性。