题目重述
小S需要通过3门必修课和n门选修课的期末考试。每门课的成绩由20道选择题决定,每题5分,答对得分,答错不得分。小S想知道,所有课程的成绩有多少种组合方式能使他的平均分不低于60分。为了简化计算,结果需要对202220222022取模。
测试样例
- 样例1:
n = 3,输出:'19195617' - 样例2:
n = 6,输出:'135464411082' - 样例3:
n = 49,输出:'174899025576' - 样例4:
n = 201,输出:'34269227409' - 样例5:
n = 888,输出:'194187156114'
题目分析
这道题目要考虑的是各种题目的得分组合,最终的目的是要把总分控制在满分的60%以上。在这个过程中并不要求每一门课都要达到及格线,从题目的数量来看,(n+3)门课程共有 20*(n+3)道题目。从得分单元来看,只需要满足对其中的20*(n+3)*0.6道即可。
题目只要求分析课程的成绩组合,所以不需要关注对的题号排列,只需要考虑不同课程里面对的题号数量即可。在这里使用DP的方式解题。
代码
为了方便解释说明,在这里先给出代码
该代码来源于网上文章启发,思路相同的代码已有多个,但给出的解释均不详细,因此我就自己根据代码推导分析了一下。十分感谢前人给出的代码
MOD = 202220222022
f = open("res.txt","w")
def solution(n):
n += 3
last_dp = [1] + [0] * 20
for i in range(1, n + 1):
MaxScore = 20 * i
curr_dp = [0] * (MaxScore + 1)
f.write(f"odp {last_dp}\n")
for j in range(0, MaxScore + 1):
for k in range(0, 21):
if j - k <= (i - 1) * 20 and j - k >= 0:
if last_dp[j - k] > 0:
curr_dp[j] = (curr_dp[j] + last_dp[j - k]) % MOD
f.write(f"j:{j} k:{k} {curr_dp}\n")
elif j - k < 0:
break
last_dp = curr_dp[:]
ans = 0
for i in range(12 * n, 20 * n + 1):
ans = (curr_dp[i] + ans) % MOD
# print(ans)
return str(ans)
if __name__ == "__main__":
# You can add more test cases here
print(solution(3) == "19195617")
print(solution(6) == "135464411082")
print(solution(49) == "174899025576")
print(solution(201) == "34269227409")
print(solution(888) == "194187156114")
代码解释
要使用DP,关键在于找到正确的动态转移方程,在本题目中,给出的动态转移方程为 curr_dp[j] = (curr_dp[j] + last_dp[j - k]) % MOD
其中还给出了应用动态转移方程的边界条件 j - k <= (i - 1) * 20 and j - k >= 0
那么,如何理解这个动态转移方程及这个边界条件呢?针对last_dp数组和curr_dp数组我们创建一个二维表方面解释说明
在这里 MaxScore = 20 * i 代表第 i 轮遍历后,总共要做的题目数,而i是由前面的的课程数n,逐轮增加的。这里的逻辑是要知道n门课得分有多少种情况,首先要知道n-1门有多少种,所以是递推进行的。
首先last_dp[0] 表示初始情况下,一个科目(20道题)一题不对的情况只有1一种,然后开始更新。 那么,只有一个科目的情况,只对一题、二题、三题、以此类推都是只有1种情况(因为我们只考虑得分,不考虑他到底是对的第几道题目)
然后,递推公式开始发力,当i=2的时候(表示开始考第二门了)对j道题目的情况实际上是对从j往前推20道求和的情况的求和。为啥呢?我们这样子考虑,两门课对0道题的情况依然是1,那么对1道题的情况是不是“另一门对0道”+“另一门对1道”;同理对2道题就是“另一门对0道”+“另一门对1道”+“另一门对2道”。
那么j - k <= (i - 1) * 20是用来干啥的?
用来限制更新到j题的时候 只去上一轮j往前推20道题目。为什么要限制只考虑前20道呢?我们以更新到第21题为例,当两门科目内需要对21道题目,则至少一门科目会对1道,因此“另一门科目对0道”就不算在内了。分析到这里,我已经对写出这个代码的大牛敬仰如滔滔江水了
本人只是算法小白,如有错误欢迎大家指正交流