青训营AI刷题 DP动态规划 及格可能组合 | 豆包MarsCode AI刷题

49 阅读4分钟

题目重述

小S需要通过3门必修课和n门选修课的期末考试。每门课的成绩由20道选择题决定,每题5分,答对得分,答错不得分。小S想知道,所有课程的成绩有多少种组合方式能使他的平均分不低于60分。为了简化计算,结果需要对202220222022取模。

测试样例

  • 样例1n = 3,输出:'19195617'
  • 样例2n = 6,输出:'135464411082'
  • 样例3n = 49,输出:'174899025576'
  • 样例4n = 201,输出:'34269227409'
  • 样例5n = 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数组我们创建一个二维表方面解释说明

图片.png

在这里 MaxScore = 20 * i 代表第 i 轮遍历后,总共要做的题目数,而i是由前面的的课程数n,逐轮增加的。这里的逻辑是要知道n门课得分有多少种情况,首先要知道n-1门有多少种,所以是递推进行的。

首先last_dp[0] 表示初始情况下,一个科目(20道题)一题不对的情况只有1一种,然后开始更新。 那么,只有一个科目的情况,只对一题、二题、三题、以此类推都是只有1种情况(因为我们只考虑得分,不考虑他到底是对的第几道题目)

图片.png

然后,递推公式开始发力,当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道”就不算在内了。分析到这里,我已经对写出这个代码的大牛敬仰如滔滔江水了

本人只是算法小白,如有错误欢迎大家指正交流