题目分析
我们有 n 张卡牌,每张卡牌的正反面分别写有不同的数字。我们可以选择让每张卡牌的正面或反面朝上,从而得到每张卡牌的一个数字。现在,我们需要找到一种选法,使得所有朝上的数字之和能够被 3 整除,并计算出满足这一条件的不同选法的总数。
由于直接计算所有组合的和非常繁琐,我们可以借助动态规划来优化这个问题。
解题思路
1. 余数状态表示
在考虑每种选法时,我们只关心最终和对 3 的余数,因为题目要求最终的和是 3 的倍数。这样我们可以将所有可能的和划分为三种余数状态:
- 余数为 0 的状态(即满足条件的状态,因为是 3 的倍数)。
- 余数为 1 的状态。
- 余数为 2 的状态。
用一个数组 dp 来记录每种余数的状态的方案数,其中:
dp[0]表示所有选法中,使得数字之和对 3 取余为 0 的方案数。dp[1]表示所有选法中,使得数字之和对 3 取余为 1 的方案数。dp[2]表示所有选法中,使得数字之和对 3 取余为 2 的方案数。
2. 初始化状态
初始情况下,我们没有选择任何卡牌,此时总和为 0,且方案数为 1。因此,dp[0] = 1 表示没有选择任何卡牌的状态,和为 0。
3. 动态转移
对于每张卡牌,我们可以选择它的正面朝上或者反面朝上。假设这张卡牌的正面数字是 a[i],反面数字是 b[i],对于已有的所有余数状态,我们可以更新为新的余数状态。更新的过程如下:
- 对于当前余数为
k的方案,若我们选择卡牌的正面数字a[i],则新的余数状态为(k + a[i]) % 3。 - 同样地,若选择卡牌的反面数字
b[i],则新的余数状态为(k + b[i]) % 3。
这样,对于每种当前余数 k,选择正反面分别会影响到不同的余数状态。这些新的余数状态可以累加到对应的 dp 值中,从而得到新的方案数。
4. 累计方案数
在每一步(即处理一张卡牌)后,我们会根据选择更新每种余数的状态的方案数。这样当我们遍历完所有卡牌后,dp[0] 中就记录了使得所有选法中总和是 3 的倍数的方案数。
5. 模运算
由于方案数可能非常大,在每次更新方案数时都要对 10^9 + 7 取模,以确保结果不会溢出。
举例说明
假设有 3 张卡牌,正反面数字分别是 (1, 2), (2, 3), (3, 2)。
-
初始化:
- 初始状态
dp = [1, 0, 0],表示和为 0 的方案数为 1,没有选择任何卡牌时的初始状态。
- 初始状态
-
处理第 1 张卡牌
(1, 2):- 当前状态为
dp = [1, 0, 0]。 - 如果选择正面
1,余数变为(0 + 1) % 3 = 1,更新dp[1] += dp[0]。 - 如果选择反面
2,余数变为(0 + 2) % 3 = 2,更新dp[2] += dp[0]。 - 结果:
dp = [1, 1, 1]。
- 当前状态为
-
处理第 2 张卡牌
(2, 3):- 当前状态为
dp = [1, 1, 1]。 - 如果选择正面
2:dp[0]更新为(dp[0] + dp[1]) % MOD。dp[1]更新为(dp[1] + dp[2]) % MOD。dp[2]更新为(dp[2] + dp[0]) % MOD。
- 当前状态为