【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【DP】2023C-分月饼【欧弟算法】全网注释最详细分类最全的华为OD真题题解

文章目录

题目描述与示例

题目描述

中秋节,公司分月饼,m个员工,买了n个月饼,m <= n,每个员工至少分1个月饼,但可以分多个,单人分到最多月饼的个数是Max1,单人分到第二多月饼个数是Max2Max1-Max2 <= 3,单人分到第n-1多月饼个数是Max(n-1),单人分到第n多月饼个数是Max(n)Max(n-1)- Max(n) <= 3,问有多少种分月饼的方法?

输入描述

每一行输入m n,表示m个员工,n个月饼,m<=n

输出描述

输出有多少种月饼分法

示例

输入

Shell 复制代码
2 4

输出

Shell 复制代码
2

说明

分法有2种:

4=1+3
4=2+2

注意: 1+33+1算一种分法

解题思路

用比较严谨的数学语言表达上述问题为:挑选出m非递减的数,要求相邻两个数的差值不超过3,这m个数的和为n,问一共有多少种挑选方式。

由于在挑选完第i个数之后,第i+1个数的取值和第i个数的取值相关,很容易想到用动态规划来解决上述问题。思考动态规划三部曲:

  1. dp数组的含义是什么?

我们需要考虑三个因素,选择了第几个数,这个数取了什么数值,当前总和为多少 。因此需要构建一个三维的dp数组。

考虑dp[i][j][k]表示,第i个数取值为j时,前i个数的总和为k的方法数。

分别考虑ijk三个数的取值范围来确定dp数组的大小。

ik的定义比较明确,范围分别是[1, m][1, n]

每个数字的取值j的最小为1,最大的情况为m-1个数字都选择最小数1,剩余一个最大数选择n-(m-1) = n-m+1。故j的取值范围是[1, n+m+1]

故构建dp数组是一个大小为(m+1)*(n-m+2)*(n+1)的三维数组。

  1. 动态转移方程是什么?

假设第i个数的取值为j,那么第i+1个数只能在jj+1j+2j+3中进行挑选。

若此时前i个数的总和为k,那么当第i+1个数

  • 取了j时,前i+1个数的总和为k+j。存在dp[i+1][j][k+j] += dp[i][j][k]
  • 取了j+1时,前i+1个数的总和为k+j+1。存在dp[i+1][j+1][k+j+1] += dp[i][j][k]
  • 取了j+2时,前i+1个数的总和为k+j+2。存在dp[i+1][j+2][k+j+2] += dp[i][j][k]
  • 取了j+3时,前i+1个数的总和为k+j+3。存在dp[i+1][j+3][k+j+3] += dp[i][j][k]

上述四个式子可以合并为一个式子,即dp[i+1][j+d][k+j+d] += dp[i][j][k],其中d的取值为[0,3]

先从小到大遍历i,再从小到大遍历j,再从小到大遍历k,则代码如下

python 复制代码
for i in range(1, m):
    for j in range(1, n-m+2):
        for k in range(i, n+1):
            for d in range(4):
                if j+d < n-m+2 and k+j+d < n+1:
                    dp[i+1][j+d][k+j+d] += dp[i][j][k]
  1. dp数组如何初始化?

i = 0没有实际意义,不考虑。

考虑i = 1的情况,第1个数字的取值j最小为1,最大为n // m(即所有数字尽可能接近的情况),即此时j的取值为[1, n // m]

同时,由于只选择了一个数字,因此此时前i个数字的总和k = j

故对于i = 1,做如下初始化

python 复制代码
dp = [[[0] * (n+1) for j in range(n-m+2)] for i in range(m+1)]
for j in range(1, n//m+1):
    dp[1][j][j] = 1

dp[1][j][j] = 1表示只对应1种方法数。

代码

Python

python 复制代码
# 题目:【DP】2023C-分月饼
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:DP
# 代码看不懂的地方,请直接在群上提问


# 输入员工人数m,月饼总数n
m, n = map(int, input().split())

# dp数组是一个大小为(m+1)*(n-m+2)*(n+1)的三维数组
# dp[i][j][k]表示,第i个数取值为j时,前i个数的总和为k的方法数
dp = [[[0] * (n+1) for j in range(n-m+2)] for i in range(m+1)]
# 第1个数字选了j,此时总和为j
# j的取值范围是[1, n//m]
# 因为m个数的和需要为n,那么最小那个数的最大值是n//m
for j in range(1, n//m+1):
    dp[1][j][j] = 1

# i的最大取值为m-1
for i in range(1, m):
    # j的最小取值为1,最大取值为n-m+1
    for j in range(1, n-m+2):
        # k的最小取值为i(前i个数都选了1,和为i),最大取值为n
        for k in range(i, n+1):
            # 增量d的取值范围为0,1,2,3
            for d in range(4):
                # 条件为j+d和k+j+d都没有超过对应的最大范围
                if j+d < n-m+2 and k+j+d < n+1:
                    dp[i+1][j+d][k+j+d] += dp[i][j][k]

ans = 0
# dp[m][j][n]表示第m个数(最后一个数)选了j后,总和为n的方法数
# 将所有的dp[m][j][n]加在一起即为答案
for j in range(n-m+2):
    ans += dp[m][j][n]

print(ans)

Java

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int m = scanner.nextInt();
        int n = scanner.nextInt();

        // dp array initialization
        int[][][] dp = new int[m + 1][n - m + 2][n + 1];
        for (int j = 1; j <= n / m; j++) {
            dp[1][j][j] = 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j <= n - m + 1; j++) {
                for (int k = i; k <= n; k++) {
                    for (int d = 0; d < 4; d++) {
                        if (j + d < n - m + 2 && k + j + d <= n) {
                            dp[i + 1][j + d][k + j + d] += dp[i][j][k];
                        }
                    }
                }
            }
        }

        int ans = 0;
        for (int j = 1; j <= n - m + 1; j++) {
            ans += dp[m][j][n];
        }

        System.out.println(ans);
    }
}

C++

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int m, n;
    cin >> m >> n;

    // dp array initialization
    int dp[m + 1][n - m + 2][n + 1] = {0};
    for (int j = 1; j <= n / m; j++) {
        dp[1][j][j] = 1;
    }

    for (int i = 1; i < m; i++) {
        for (int j = 1; j <= n - m + 1; j++) {
            for (int k = i; k <= n; k++) {
                for (int d = 0; d < 4; d++) {
                    if (j + d < n - m + 2 && k + j + d <= n) {
                        dp[i + 1][j + d][k + j + d] += dp[i][j][k];
                    }
                }
            }
        }
    }

    int ans = 0;
    for (int j = 1; j <= n - m + 1; j++) {
        ans += dp[m][j][n];
    }

    cout << ans << endl;

    return 0;
}

时空复杂度

时间复杂度:O(NM(N-M))。三重循环所需时间复杂度。

空间复杂度:O(NM(N-M))。三维dp数组所需空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

相关推荐
浮生如梦_1 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
深度学习lover1 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
API快乐传递者2 小时前
淘宝反爬虫机制的主要手段有哪些?
爬虫·python
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
师太,答应老衲吧3 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法