当前位置: 首页 > news >正文

华为OD机试C卷 - 分披萨 - 贪心 DFS - (Java C++ JavaScript Python)

一、题目描述

“吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨,并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。

但是粗心服务员将披萨切成了每块大小都完全不同奇数块,且肉眼能分辨出大小。

由于两人都想吃到最多的披萨,他们商量了一个他们认为公平的分法:从“吃货”开始,轮流取披萨。

除了第一块披萨可以任意选取以外,其他都必须从缺口开始选。 他俩选披萨的思路不同。

“馋嘴”每次都会选最大块的披萨,而且“吃货”知道“馋嘴”的想法。

已知披萨小块的数量以及每块的大小,求“吃货”能分得的最大的披萨大小的总和。

二、输入描述

第1行为一个正整数奇数 N ,表示披萨小块数量。其中 3 ≤ N< 500

接下来的第 2 行到第 N+1 (共 N 行),每行为一个正整数,表示第i块披萨的大小, 1≤i≤N 。

披萨小块从某一块开始,按照一个方向次序顺序编号为 1 ~ N ,每块披萨的大小范围为[1,2147483647]。

三、输出描述

”吃货“能分得到的最大的披萨大小的总和。

四、测试用例

用例1
输入

3

1

2

3

输出

4

说明

披萨被切成 3 块,大小分别为 1、2、3。

"吃货"最优策略是先选 3,然后 “馋嘴” 会选 2,最后 “吃货” 选 1。

因此,“吃货” 能获得的最大总和是 3 + 1 = 4。

用例2
输入

7

10

20

30

40

50

60

70

输出

160

五、解题思路

  1. “吃货”第一块可任选 → 枚举所有 n 个起始位置,取最大值。
  2. “馋嘴”行为确定(贪心),因此“吃货”可以预判对方选择。
  3. 游戏状态由缺口决定:一旦第一块选定,剩余披萨形成一个连续的环形区间 [l, r]
  4. 回合顺序固定
    • “吃货”先拿第一块。
    • 然后“馋嘴”贪心选。
    • 然后“吃货”做最优选择。
    • 如此交替。
  5. 可用 记忆化搜索(DFS + 缓存) 求解子问题。
  6. 定义 dp(l, r):因为“吃货”拿完第一块后,下一个是“馋嘴”,所以递归入口是“馋嘴回合”。
  7. “馋嘴”贪心选择
    • 比较 pizza[l] 和 pizza[r],选择较大的一块。
    • 更新边界 newLeft 或 newRight
  8. 判断是否只剩一块
    • 如果只剩一块,轮到“吃货”,他直接拿走。
  9. “吃货”做最优选择
    • 他可以选择新的左端或右端。
    • 递归计算两种选择的收益,取最大值。

六、Java源码实现

private static int n;              // 披萨块的数量private static long[] pizza;       // 存储每块披萨的大小private static Long[][] memo;      // 记忆化数组:memo[l][r] 表示在区间 [l,r] 且轮到“馋嘴”时,“吃货”后续能获得的最大值public static void main(String[] args) {Scanner sc = new Scanner(System.in);n = Integer.parseInt(sc.nextLine().trim());  // 读入披萨块数pizza = new long[n];for (int i = 0; i < n; i++) {pizza[i] = Long.parseLong(sc.nextLine().trim());  // 读入每块披萨的大小}// 初始化记忆化数组(全局使用,避免重复计算)memo = new Long[n][n];long maxSum = 0;  // 记录“吃货”能获得的最大总和// 枚举“吃货”第一块的选择(从每一块开始尝试)for (int start = 0; start < n; start++) {// 计算选择第 start 块后,剩余披萨的左右边界// 左边界:start 的下一个块(顺时针)int left = (start + 1) % n;// 右边界:start 的前一个块(逆时针)int right = (start - 1 + n) % n;long rest;  // 记录选择 start 后,“吃货”还能获得的额外总和if (left == right) {// 特判:只剩一块,吃货直接拿走rest = pizza[left];} else {// 否则,进入递归,计算从区间 [left, right] 开始,“吃货”后续能获得的最大值rest = dp(left, right);}// 总和 = 第一块 + 后续最大收益long total = pizza[start] + rest;maxSum = Math.max(maxSum, total);  // 更新最大值}// 输出最终结果System.out.println(maxSum);}/*** dp(l, r):当前可选区间为 [l, r](闭区间),轮到“馋嘴”选择时,* “吃货”后续能获得的最大披萨总和。** @param l 当前可选区间的左端点(包含)* @param r 当前可选区间的右端点(包含)* @return “吃货”能获得的最大总和*/private static long dp(int l, int r) {// 检查记忆化数组,避免重复计算if (memo[l][r] != null) {return memo[l][r];}// 模拟“馋嘴”的贪心选择int newLeft = l;int newRight = r;if (pizza[l] >= pizza[r]) {// “馋嘴”选择左边的披萨newLeft = (l + 1) % n;  // 左边界右移} else {// “馋嘴”选择右边的披萨newRight = (r - 1 + n) % n;  // 右边界左移(+n 防止负数)}// 判断“馋嘴”选完后是否只剩一块if (newLeft == newRight) {// 是的,只剩一块,轮到“吃货”,他直接拿走return memo[l][r] = pizza[newLeft];}// “吃货”现在有两个选择:拿 newLeft 或 newRight// 他要选择能让自己总和最大的方案// 选择左边的披萨long choiceLeft = pizza[newLeft] + dp((newLeft + 1) % n, newRight);// 选择右边的披萨long choiceRight = pizza[newRight] + dp(newLeft, (newRight - 1 + n) % n);// 记录并返回最大值long result = Math.max(choiceLeft, choiceRight);memo[l][r] = result;return result;}
运行结果

七、C++源码实现

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;// 全局变量
int n;
vector<long long> pizza;        // 披萨每块的大小
vector<vector<long long>> memo; // 记忆化数组:memo[l][r] 表示在区间 [l,r] 且轮到“馋嘴”时,“吃货”后续能获得的最大值/*** dp(l, r):当前可选区间为 [l, r](闭区间),轮到“馋嘴”选择时,* “吃货”后续能获得的最大披萨总和。** @param l 当前可选区间的左端点(包含)* @param r 当前可选区间的右端点(包含)* @return “吃货”能获得的最大总和*/
long long dp(int l, int r) {// 基本情况:如果只剩一块(l == r),轮到“馋嘴”,他会拿走这块// “吃货”无法再获得,返回 0if (l == r) {return 0;}// 检查记忆化数组,避免重复计算if (memo[l][r] != -1) {return memo[l][r];}// 模拟“馋嘴”的贪心选择int newLeft = l;int newRight = r;if (pizza[l] >= pizza[r]) {// “馋嘴”选择左边的披萨newLeft = (l + 1) % n;  // 左边界右移} else {// “馋嘴”选择右边的披萨newRight = (r - 1 + n) % n;  // 右边界左移(+n 防止负数)}// 判断“馋嘴”选完后是否只剩一块if (newLeft == newRight) {// 是的,只剩一块,轮到“吃货”,他直接拿走memo[l][r] = pizza[newLeft];return memo[l][r];}// “吃货”现在有两个选择:拿 newLeft 或 newRight// 他要选择能让自己总和最大的方案// 选择左边的披萨long long choiceLeft = pizza[newLeft] + dp((newLeft + 1) % n, newRight);// 选择右边的披萨long long choiceRight = pizza[newRight] + dp(newLeft, (newRight - 1 + n) % n);// 记录并返回最大值long long result = max(choiceLeft, choiceRight);memo[l][r] = result;return result;
}int main() {cin >> n;pizza.resize(n);for (int i = 0; i < n; i++) {cin >> pizza[i];}// 初始化记忆化数组为 -1(表示未计算)memo.assign(n, vector<long long>(n, -1));long long maxSum = 0;  // 记录“吃货”能获得的最大总和// 枚举“吃货”第一块的选择(从每一块开始尝试)for (int start = 0; start < n; start++) {// 计算选择第 start 块后,剩余披萨的左右边界int left = (start + 1) % n;           // 左边界:start 的下一个块int right = (start - 1 + n) % n;      // 右边界:start 的前一个块long long rest;  // 记录选择 start 后,“吃货”还能获得的额外总和if (left == right) {// 特判:只剩一块,吃货直接拿走rest = pizza[left];} else {// 否则,进入递归,计算从区间 [left, right] 开始,“吃货”后续能获得的最大值rest = dp(left, right);}// 总和 = 第一块 + 后续最大收益long long total = pizza[start] + rest;maxSum = max(maxSum, total);  // 更新最大值}// 输出最终结果cout << maxSum << endl;return 0;
}

八、JavaScript源码实现

/*** 主函数:解决披萨博弈问题* - 吃货先手,可任选第一块* - 馋嘴总是贪心选择当前可选两端中较大的一块* - 吃货希望最大化自己总和* - 返回吃货能获得的最大总和*/
function main() {const input = require('readline-sync'); // 使用 readline-sync 读取输入(Node.js 环境)// 读取披萨块数const n = parseInt(input.question(''));// 读取每块披萨的大小const pizza = [];for (let i = 0; i < n; i++) {pizza.push(BigInt(input.question(''))); // 使用 BigInt 防止大数溢出}// 记忆化数组:memo[l][r] 表示在区间 [l,r] 且轮到“馋嘴”时,“吃货”后续能获得的最大值// 初始化为 null 表示未计算const memo = Array(n).fill(null).map(() => Array(n).fill(null));let maxSum = 0n; // 使用 BigInt,初始为 0n// 枚举“吃货”第一块的选择(从每一块开始尝试)for (let start = 0; start < n; start++) {// 计算选择第 start 块后,剩余披萨的左右边界const left = (start + 1) % n;           // 左边界:start 的下一个块const right = (start - 1 + n) % n;      // 右边界:start 的前一个块let rest; // 记录选择 start 后,“吃货”还能获得的额外总和if (left === right) {// 特判:只剩一块,吃货直接拿走rest = pizza[left];} else {// 否则,进入递归,计算从区间 [left, right] 开始,“吃货”后续能获得的最大值rest = dp(left, right, n, pizza, memo);}// 总和 = 第一块 + 后续最大收益const total = pizza[start] + rest;if (total > maxSum) {maxSum = total;}}// 输出最终结果console.log(maxSum.toString()); // 输出 BigInt 为字符串
}/*** dp(l, r):当前可选区间为 [l, r](闭区间),轮到“馋嘴”选择时,* “吃货”后续能获得的最大披萨总和。** @param {number} l - 当前可选区间的左端点(包含)* @param {number} r - 当前可选区间的右端点(包含)* @param {number} n - 披萨总块数* @param {BigInt[]} pizza - 披萨每块的大小数组* @param {(BigInt|null)[][]} memo - 记忆化数组* @return {BigInt} - “吃货”能获得的最大总和*/
function dp(l, r, n, pizza, memo) {// 基本情况:如果只剩一块(l == r),轮到“馋嘴”,他会拿走这块// “吃货”无法再获得,返回 0if (l === r) {return 0n;}// 检查记忆化数组,避免重复计算if (memo[l][r] !== null) {return memo[l][r];}// 模拟“馋嘴”的贪心选择let newLeft = l;let newRight = r;if (pizza[l] >= pizza[r]) {// “馋嘴”选择左边的披萨newLeft = (l + 1) % n;  // 左边界右移} else {// “馋嘴”选择右边的披萨newRight = (r - 1 + n) % n;  // 右边界左移(+n 防止负数)}// 判断“馋嘴”选完后是否只剩一块if (newLeft === newRight) {// 是的,只剩一块,轮到“吃货”,他直接拿走memo[l][r] = pizza[newLeft];return memo[l][r];}// “吃货”现在有两个选择:拿 newLeft 或 newRight// 他要选择能让自己总和最大的方案// 选择左边的披萨const choiceLeft = pizza[newLeft] + dp((newLeft + 1) % n, newRight, n, pizza, memo);// 选择右边的披萨const choiceRight = pizza[newRight] + dp(newLeft, (newRight - 1 + n) % n, n, pizza, memo);// 记录并返回最大值const result = choiceLeft > choiceRight ? choiceLeft : choiceRight;memo[l][r] = result;return result;
}// 执行主函数
main();

九、Python源码实现

import sys
from functools import lru_cachedef main():# 读取输入n = int(sys.stdin.readline().strip())pizza = []for _ in range(n):pizza.append(int(sys.stdin.readline().strip()))# 使用 lru_cache 实现记忆化,替代二维数组@lru_cache(maxsize=None)def dp(l, r):"""dp(l, r): 当前可选区间为 [l, r],轮到“馋嘴”选择时,“吃货”后续能获得的最大披萨总和。参数:l (int): 当前可选区间的左端点(包含)r (int): 当前可选区间的右端点(包含)返回:int: “吃货”能获得的最大总和"""# 基本情况:如果只剩一块(l == r),轮到“馋嘴”,他会拿走这块# “吃货”无法再获得,返回 0if l == r:return 0# 模拟“馋嘴”的贪心选择if pizza[l] >= pizza[r]:new_left = (l + 1) % nnew_right = relse:new_left = lnew_right = (r - 1) % n# 判断“馋嘴”选完后是否只剩一块if new_left == new_right:# 是的,只剩一块,轮到“吃货”,他直接拿走return pizza[new_left]# “吃货”现在有两个选择:拿 new_left 或 new_right# 他要选择能让自己总和最大的方案# 选择左边的披萨choice_left = pizza[new_left] + dp((new_left + 1) % n, new_right)# 选择右边的披萨choice_right = pizza[new_right] + dp(new_left, (new_right - 1) % n)# 返回最大值return max(choice_left, choice_right)max_sum = 0  # 记录“吃货”能获得的最大总和# 枚举“吃货”第一块的选择(从每一块开始尝试)for start in range(n):# 计算选择第 start 块后,剩余披萨的左右边界left = (start + 1) % n           # 左边界:start 的下一个块right = (start - 1) % n          # 右边界:start 的前一个块(Python 负数取模自动处理)rest = 0  # 记录选择 start 后,“吃货”还能获得的额外总和if left == right:# 特判:只剩一块,吃货直接拿走rest = pizza[left]else:# 否则,进入递归,计算从区间 [left, right] 开始,“吃货”后续能获得的最大值rest = dp(left, right)# 总和 = 第一块 + 后续最大收益total = pizza[start] + restif total > max_sum:max_sum = total# 输出最终结果print(max_sum)# 执行主函数
if __name__ == "__main__":main()

http://www.dtcms.com/a/445698.html

相关文章:

  • 仿照STM32 HAL库设计思想使用FreeRTOS实现异步非阻塞式设备驱动
  • 铜川做网站电话网页设计规范
  • ssc-FinLLM 金融大模型 相关链接
  • 二叉排序树(建树、查找、删除)
  • Linux学习笔记--i2cget 命令
  • 网站建设的标签指的是响应式网页设计图片
  • 用 CodeBuddy CLI + Prompt,从零到可运行:前后端混合管理系统的高效实战
  • 电源——电荷泵详解
  • 榆林国贸网站建设网站的关键词挖掘方式
  • 从思路到落地:用 Redis 搭建超低延迟在线特征存储库
  • sosdp
  • 快速了解BERT
  • 在线Deflate压缩工具(支持添加zlib头及无zlib头模式)
  • 第14节-增强表结构-ALTER-TABLE
  • Ubuntu之apt更新源
  • T527 IR-RX 调试
  • 低成本能谱仪设计:基于分立器件的模拟前端与数字后端实现方案
  • 计算机视觉的数据收集与标注
  • LeetCode:92.最小路径和
  • 百度竞价推广属于什么广告广东网站se0优化公司
  • Anaconda路径配置
  • GitHub 热榜项目 - 日榜(2025-10-05)
  • java中Math.random()和random()方法区别
  • Django SimpleUI 详解:现代化的Django Admin界面美化方案
  • 网站开发客户流程 6个阶段简述常用的网站开发软件
  • 区块链分层学:新的开始
  • Qt与CMakeLists.txt
  • Ubuntu20.04安装Anaconda3-2025.06
  • VirtualBox中ubuntu1804虚拟机共享文件夹设置
  • 基于单片机的环境监测智能报警系统的设计(论文+源码)