题意整理
- 牛牛准备出一套卷子,共n个多项选择题。
- 正确答案中,A和C出现次数差距不超过1,B和D出现次数差距不超过2。
- 问有多少种符合要求的正确答案。
方法一(动态规划)
1.解题思路
正确答案主要由两个因素决定它的种类数,一是A和C出现次数差距,二是B和D出现次数差距,可以通过这两个因素确定15种状态,每种状态的方案数累加,即是正确答案的数量。
A和C出现次数差距可以是-1,0,1,B和D出现次数差距可以是-2,-1,0,1,2,将参数对齐到大于等于0之后,分别对应j=0,1,2以及k=0,1,2,3,4
mask从1到15分别对应15种答案,其对应关系如下表所示:
mask | 答案 |
---|---|
0001 | D |
0010 | C |
0011 | CD |
0100 | B |
0101 | BD |
0110 | BC |
0111 | BCD |
1000 | A |
1001 | AD |
1010 | AC |
1011 | ACD |
1100 | AB |
1101 | ABD |
1110 | ABC |
1111 | ABCD |
- 状态定义:中i表示题目数量,j表示A、C出现次数差值,k表示B、D出现次数差值,表示对应状态的方案数。
- 状态初始化:当题目只有一个时,初始化每种方案的数量。
- 状态转移:每增加一个题目,对应的A、C出现次数差值和B、D出现次数差值就可能发生变化,根据mask对应的15种答案,如果j、k变化后仍然在平衡范围内,则跟新对应的状态,即。
2.代码实现
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @return int整型 */ //取余常数 int mod=1000000007; public int solve (int n) { //初始化dp数组,dp[i][j][k]中i表示题目数量,j表示A、C出现次数差值,k表示B、D出现次数差值 int[][][] dp=new int[n+1][3][5]; //只有一个题目时的情况 for(int mask=1;mask<=15;mask++){ //mask为对应的15种答案,A、B、C、D分别表示每个答案中对应A、B、C、D出现次数 int A=(mask>>3)&1,B=(mask>>2)&1,C=(mask>>1)&1,D=mask&1; dp[1][1+A-C][2+B-D]++; } for(int i=2;i<=n;i++){ for(int j=0;j<3;j++){ for(int k=0;k<5;k++){ for(int mask=1;mask<=15;mask++){ int A=(mask>>3)&1,B=(mask>>2)&1,C=(mask>>1)&1,D=mask&1; //如果加上一题后,对应差值仍在平衡范围内,则跟新状态 if(j+A-C>=0&&j+A-C<3&&k+B-D>=0&&k+B-D<5){ dp[i][j+A-C][k+B-D]=(dp[i][j+A-C][k+B-D]+dp[i-1][j][k])%mod; } } } } } int res=0; //将每种状态对应的方案数累加 for(int j=0;j<3;j++){ for(int k=0;k<5;k++){ res=(res+dp[n][j][k])%mod; } } return res; } }
3.复杂度分析
- 时间复杂度:循环体最多执行次,所以时间复杂度是。
- 空间复杂度:需要额外大小为的dp数组,所以空间复杂度是。
方法二(空间压缩)
1.解题思路
由于方法一中,每增加一题之后的状态只与对应的未增加这一题时的状态有关,所以可以通过滚动数组的方式压缩空间,dp[0][j][k]表示未增加某一题时的状态,dp[1][j][k]表示增加这一题之后的状态。
动图展示:
2.代码实现
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @return int整型 */ //初始化取余常数 int mod=1000000007; public int solve (int n) { //初始化dp数组,dp[i][j][k]中i表示滚动数组变量,j表示A、C出现次数差值,k表示B、D出现次数差值 int[][][] dp=new int[2][3][5]; //给dp[0][j][k]赋初值 for(int mask=1;mask<=15;mask++){ //mask为对应的15种答案,A、B、C、D分别表示每个答案中对应A、B、C、D出现次数 int A=(mask>>3)&1,B=(mask>>2)&1,C=(mask>>1)&1,D=mask&1; //A-C范围在[-1,1],B-D范围在[-2,2],由于下标从0开始,每个状态需要加常数进行对齐 dp[0][1+A-C][2+B-D]++; } for(int i=1;i<n;i++){ for(int j=0;j<3;j++){ for(int k=0;k<5;k++){ for(int mask=1;mask<=15;mask++){ int A=(mask>>3)&1,B=(mask>>2)&1,C=(mask>>1)&1,D=mask&1; //如果加上一题后,对应差值仍在平衡范围内,则跟新状态 if(j+A-C>=0&&j+A-C<3&&k+B-D>=0&&k+B-D<5){ dp[1][j+A-C][k+B-D]=(dp[1][j+A-C][k+B-D]+dp[0][j][k])%mod; } } } } //跟新完状态后,将i为1的dp数组赋值给i为0的dp数组,初始化i为1的dp数组,为下次跟新状态做准备 System.out.println(); for(int j=0;j<3;j++){ for(int k=0;k<5;k++){ dp[0][j][k]=dp[1][j][k]; dp[1][j][k]=0; } } } int res=0; //将每种状态对应的方案数累加 for(int j=0;j<3;j++){ for(int k=0;k<5;k++){ res=(res+dp[0][j][k])%mod; } } return res; } }
3.复杂度分析
- 时间复杂度:循环体最多执行次,所以时间复杂度是。
- 空间复杂度:需要额外大小为常数级别的dp数组,所以空间复杂度是。