华为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],选择较大的一块。
    • 更新边界 newLeftnewRight
  8. 判断是否只剩一块
    • 如果只剩一块,轮到"吃货",他直接拿走。
  9. "吃货"做最优选择
    • 他可以选择新的左端或右端。
    • 递归计算两种选择的收益,取最大值。

六、Java源码实现

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++源码实现

cpp 复制代码
#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),轮到"馋嘴",他会拿走这块
    // "吃货"无法再获得,返回 0
    if (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源码实现

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),轮到"馋嘴",他会拿走这块
    // "吃货"无法再获得,返回 0
    if (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源码实现

python 复制代码
import sys
from functools import lru_cache

def 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),轮到"馋嘴",他会拿走这块
        # "吃货"无法再获得,返回 0
        if l == r:
            return 0
        
        # 模拟"馋嘴"的贪心选择
        if pizza[l] >= pizza[r]:
            new_left = (l + 1) % n
            new_right = r
        else:
            new_left = l
            new_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] + rest
        if total > max_sum:
            max_sum = total
    
    # 输出最终结果
    print(max_sum)

# 执行主函数
if __name__ == "__main__":
    main()
相关推荐
天选之女wow13 小时前
【代码随想录算法训练营——Day28】贪心算法——134.加油站、135.分发糖果、860.柠檬水找零、406.根据身高重建队列
算法·leetcode·贪心算法
Gohldg13 小时前
C++算法·贪心例题讲解
c++·数学·算法·贪心算法
Miraitowa_cheems2 天前
LeetCode算法日记 - Day 63: 图像渲染、岛屿数量
java·数据结构·算法·leetcode·决策树·贪心算法·深度优先
smallnetter3 天前
华为OD机试C卷 - 分苹果 - 二进制 - (Java & C++ & JavaScript & Python)
算法·华为od
天选之女wow4 天前
【代码随想录算法训练营——Day27(Day26休息)】贪心算法——455.分发饼干、376.摆动序列、53.最大子数组和
算法·leetcode·贪心算法
我是华为OD~HR~栗栗呀6 天前
Java面经(22届考研-华oD)
java·后端·python·华为od·华为
小邓儿◑.◑8 天前
贪心算法 | 每周8题(一)
算法·贪心算法
闲人编程10 天前
会议安排问题之贪心算法
python·算法·ios·贪心算法·会议问题·算法改进·codecapsule
今天也好累11 天前
贪心算法之船舶装载问题
c++·笔记·学习·算法·贪心算法