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

77 阅读4分钟

题目描述

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

例如:如果有3张卡牌,正反面数字分别为 (1,2)(2,3)(3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。

输入

n=3,a=[1,2,3],b=[2,3,2]n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]

输出

33

算法

动态规划

动态规划思路

为了方面,下面卡牌的起始位置从0开始。

这道题的核心是使用动态规划来解决组合问题,通过状态转移来累积结果。我们定义两个状态数组 dp1dp2

  • dp1[i][j] 表示选择第i张卡牌的正面,使得从第0张卡牌到第i张卡牌的组合数字之和的余数为j的方案数量。
  • dp2[i][j] 表示选择第i张卡牌的反面,使得从第0张卡牌到第i张卡牌的组合数字之和的余数为j的方案数量。

其中,j的值可以是0, 1, 或 2,因为我们只关心和对3的余数。

状态转移方程

对于每张卡牌,我们要更新其正反两面各自的状态:

  1. 当前卡牌的正面数字是a[i],反面数字是b[i]

  2. 对于前i-1张卡牌的每种余数状态j(0, 1, 2),我们有以下转移规则:

    • 若第i张卡牌选择正面,其和的余数更新为 (a[i] + j) % 3
    • 若第i张卡牌选择反面,其和的余数更新为 (b[i] + j) % 3

因此,状态转移公式为:

 dp1[i][(a[i] + j) % 3] += (dp1[i - 1][j] + dp2[i - 1][j]) % mod;
 dp1[i][(a[i] + j) % 3] %= mod;
 ​
 dp2[i][(b[i] + j) % 3] += (dp1[i - 1][j] + dp2[i - 1][j]) % mod;
 dp2[i][(b[i] + j) % 3] %= mod;
  • 解释

    • dp1[i][(a[i] + j) % 3]:当前选择第i张卡牌的正面,那么和的余数将变为 (a[i] + j) % 3。我们从前一张卡牌的正反面余数状态 dp1[i-1][j]dp2[i-1][j] 转移而来,因此累加它们的方案数。
    • dp2[i][(b[i] + j) % 3]:当前选择第i张卡牌的反面,那么和的余数将变为 (b[i] + j) % 3,从前一张卡牌的正反面余数状态 dp1[i-1][j]dp2[i-1][j] 转移而来。

初始化

对于第0张卡牌,我们直接初始化dp1dp2的初始状态:

  • dp1[0][a[0] % 3] = 1 表示若选择第0张卡片的正面,其余数状态为 a[0] % 3
  • dp2[0][b[0] % 3] = 1 表示若选择第0张卡片的反面,其余数状态为 b[0] % 3

终止条件和结果提取

遍历完所有卡片后,我们得到dp1[n-1][0]dp2[n-1][0],分别表示在选择所有卡片的正反面时,和能被3整除的方案数。最终答案为它们之和:

 (dp1[n - 1][0] + dp2[n - 1][0]) % mod;

复杂度分析

  • 时间复杂度O(n * 3),对于每张卡片,我们更新3种余数状态(0, 1, 2),因此总复杂度是O(3 * n) ≈ O(n)
  • 空间复杂度O(n * 3 * 2),因为我们有两个n * 3的状态数组dp1dp2

总结

这道题的难点在于:

  1. 卡牌正反面选择带来的双状态设计,即需要分别考虑正反两种选择路径。
  2. 余数状态的动态规划转移,确保每步的和对3的余数被正确累积。
  3. 取模处理,由于组合数可能非常大,每次累加都需要进行取模操作以避免溢出。

通过上述动态规划方法,我们能在时间复杂度为O(n)的情况下有效地求解方案数。

代码

 constexpr int mod = 1e9 + 7;
 int solution(int n, std::vector<int> a, std::vector<int> b) {
     // write code here
     
     std::vector<std::vector<int> > dp1(n, std::vector<int>(3, 0));
     std::vector<std::vector<int> > dp2(n, std::vector<int>(3, 0));
     dp1[0][a[0] % 3] = 1;
     dp2[0][b[0] % 3] = 1;
     for (int i = 1; i < n; i ++) {
         a[i] %= 3;
         b[i] %= 3;
         for (int j = 0; j < 3; j ++) {
             dp1[i][(a[i] + j) % 3] += (dp1[i - 1][j] + dp2[i - 1][j]) % mod;
             dp1[i][(a[i] + j) % 3] %= mod;
             dp2[i][(b[i] + j) % 3] += (dp1[i - 1][j] + dp2[i - 1][j]) % mod;
             dp2[i][(b[i] + j) % 3] %= mod;
         }
     }
     return (dp1[n - 1][0] + dp2[n - 1][0]) % mod; // Placeholder return
 }