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