题解:学而思编程 美食评委
【题目来源】
学而思编程:美食评委
【题目描述】
一年一度的美食比赛火热进行中,小猴作为评委需要对一些选手的菜品打分,所有菜品从左到右排成一排,编号依次为 \(1∼n\),第 \(i\) 个菜品的美味值是 \(d_i\)。小猴可以自己选择对那些选手的菜品进行打分,但是必须满足以下两个条件:
① 至少选择两位选手的菜品;
② 选择的第一个选手和最后一位选手的菜品美味值必须相同。
小猴所选的第一个菜品和最后一个菜品之间(不包含第一个和最后一个菜品)的部分菜品可以选择不要,请你帮助小猴计算所选菜品美味值的总和的最大值是多少?
【输入】
第一行一个整数 \(n\);
第二行 \(n\) 个整数 \(d_1,d_2,\dots,d_n\)。
【输出】
一行一个整数,表示所选菜品美味值的总和的最大值。
【输入样例】
5
1 2 3 1 2
【输出样例】
8
【核心思想】
-
问题分析:给定长度为 \(n\) 的数列 \(d_1, d_2, \ldots, d_n\),要求选择至少两个元素,且首尾元素值相同,中间元素可任意取舍,最大化所选元素之和。这是一个贪心累加 + 哈希映射问题,关键在于将问题转化为"以相同值为锚点的最大子段和",并用哈希表记录首次出现时的前缀和状态。
-
算法选择:
- 正数贪心累加:维护
sum只累加正数元素,作为中间可选部分的最大贡献 - 哈希映射首次出现:
f[v]记录值 \(v\) 第一次出现时的sum状态(即该值作为首元素时,中间正数累加和) - 锚点配对:当值 \(v\) 再次出现时,计算以该值为首尾的最大子段和
- 正数贪心累加:维护
-
关键步骤:
- 初始化:
- 读取 \(n\) 和数组 \(d[1..n]\)
f[v]初始化为 \(-\infty\)(表示值 \(v\) 尚未出现),因 \(d_i\) 可能为负,用偏移量+10000处理下标sum = 0:当前正数累加和,ans = -\infty
- 遍历数组(\(i\) 从 \(1\) 到 \(n\)):
- 尝试配对(若 \(d_i\) 之前出现过):
ans = \max(ans, sum - f[d_i + 10000] + 2 \times d_i)- 解释:
sum为当前累计正数和,f[...]为该值首次出现时的正数和,差值即为两次出现之间所有正数之和,加上首尾两个 \(d_i\)
- 维护正数和:
- 若
d[i] > 0:sum += d[i](只累加正数,负数对中间段无贡献)
- 若
- 记录首次出现:
- 若
f[d_i + 10000] == -\infty:f[d_i + 10000] = sum(记录该值首次出现时的正数累加状态)
- 若
- 尝试配对(若 \(d_i\) 之前出现过):
- 输出
ans
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),单次遍历数组
- 空间复杂度:\(O(V)\),\(V\) 为值域大小(本题约 \(20000\)),哈希表
f
-
贪心正数累加的核心思想:
- 问题转化:所选子段 = 首元素 \(v\) + 中间可选部分 + 尾元素 \(v\),要求最大化。中间部分只选正数最优,因此用
sum维护到当前位置的所有正数之和 - 首次出现记录:
f[v]记录值 \(v\) 第一次出现时的sum,作为"锚点基准"。当 \(v\) 再次出现时,两次sum之差即为两锚点间所有正数之和 - 配对公式推导:
当前sum - 首次sum + v + v= 中间正数和 + 首尾两个 \(v\),等价于子段 \([first\_pos, i]\) 中所有正数加上首尾 \(v\) - 负数处理:若 \(d_i < 0\),不加入
sum(中间不选),但可作为首尾锚点(公式中已加 \(2 \times d_i\)) - 至少两个元素的保证:配对时自然需要首尾两个相同值,满足条件①
- 适用于"首尾相同值约束下的最大子段和"问题,贪心正数累加配合哈希映射可将复杂枚举优化为线性扫描
- 问题转化:所选子段 = 首元素 \(v\) + 中间可选部分 + 尾元素 \(v\),要求最大化。中间部分只选正数最优,因此用
【解题思路】

【算法标签】
模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
long long n, d[1000005], f[20005], sum, ans = -2e9; // ans初始化为负无穷
int main()
{
cin >> n; // 输入数组长度
for (int i = 1; i <= n; i++) // 输入数组元素
{
cin >> d[i];
}
// 初始化f数组为负无穷
for (int i = 1; i <= 20000; i++)
{
f[i] = -2e9;
}
for (int i = 1; i <= n; i++) // 遍历数组
{
// 如果之前出现过相同的数
if (f[d[i] + 10000] != -2e9)
{
// 计算当前子段和:sum - f[d[i]+10000] + 2*d[i]
ans = max(ans, sum - f[d[i] + 10000] + 2 * d[i]);
}
// 只累加正数到sum
if (d[i] > 0)
{
sum += d[i];
}
// 如果这个数是第一次出现,记录当前位置的sum
if (f[d[i] + 10000] == -2e9)
{
f[d[i] + 10000] = sum;
}
}
cout << ans << endl; // 输出最大子段和
return 0;
}
【运行结果】
5
1 2 3 1 2
8
浙公网安备 33010602011771号