一、题目描述
"吃货"和"馋嘴"两人到披萨店点了一份铁盘(圆形)披萨,并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。
但是粗心服务员将披萨切成了每块大小都完全不同奇数块,且肉眼能分辨出大小。
由于两人都想吃到最多的披萨,他们商量了一个他们认为公平的分法:从"吃货"开始,轮流取披萨。
除了第一块披萨可以任意选取以外,其他都必须从缺口开始选。 他俩选披萨的思路不同。
"馋嘴"每次都会选最大块的披萨,而且"吃货"知道"馋嘴"的想法。
已知披萨小块的数量以及每块的大小,求"吃货"能分得的最大的披萨大小的总和。
二、输入描述
第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源码实现
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()