1.问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
2.问题解决代码及注释(java)
初始化动态规划数组,dp[i][j][k] 表示前i张卡牌,余数为j,是否选择第i张卡牌正面的方案数
int[][][] dp = new int[n + 1][3][2];
dp[0][0][0] = 1;
遍历所有可能的余数;选择第i张卡牌的正面,更新余数;选择第i张卡牌的反面,更新余数
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 3; j++) {
dp[i][j][1] = (dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][0] + dp[i - 1][(j - a[i - 1] % 3 + 3) % 3][1]) % MOD;
dp[i][j][0] = (dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][0] + dp[i - 1][(j - b[i - 1] % 3 + 3) % 3][1]) % MOD;
}
}
最终结果为所有卡牌都考虑后,余数为0的方案数之和
return (dp[n][0][0] + dp[n][0][1]) % MOD;
3.程序UML类图
4.程序数据结构
本段Java代码是一个动态规划算法的实现,用于解决一个特定的问题。程序中定义了一个solution函数,它接受三个参数:n表示卡牌的数量,a数组表示每张卡牌正面的值,b数组表示每张卡牌反面的值。目标是计算出所有可能的卡牌组合的和的方案数,其中每张卡牌可以选择正面朝上或反面朝上。
-
MOD常量:
private static final int MOD = 1000000007;定义了一个常量MOD,用于在计算过程中取模,以避免整数溢出,并符合某些数学问题中对结果模一个特定数的要求。
-
dp数组:
int[][][] dp = new int[n + 1][3][2];定义了一个三维数组dp,用于存储动态规划的状态。dp[i][j][k]的含义如下:i表示当前考虑的卡牌编号(从1到n)。j表示当前卡牌的和的模3余数(0,1,2)。k表示是否选择了卡牌的正面(0表示反面,1表示正面)。
-
初始化:
dp[0][0][0] = 1;表示在没有选择任何卡牌时,和为0,且没有选择正面,这是一个基础情况。
-
状态转移方程:
- 外层循环
for (int i = 1; i <= n; i++)遍历每张卡牌。 - 中层循环
for (int j = 0; j < 3; j++)遍历所有可能的和的模3余数。 - 内层计算
dp[i][j][1]和dp[i][j][0]分别表示选择当前卡牌正面和反面时的方案数。 dp[i][j][1]的计算涉及到dp[i - 1]的状态,表示上一张卡牌的状态,通过a[i - 1] % 3计算当前卡牌正面值对和的模3余数的影响,并结合上一张卡牌的状态来更新当前状态。dp[i][j][0]的计算方式类似,但是使用b[i - 1] % 3来计算反面值的影响。
- 外层循环
-
最终结果:
return (dp[n][0][0] + dp[n][0][1]) % MOD;表示最终结果,即所有可能的卡牌组合的和的方案数,包括正面和反面的情况。