题解:学而思编程 美食评委

【题目来源】

学而思编程:美食评委

【题目描述】

一年一度的美食比赛火热进行中,小猴作为评委需要对一些选手的菜品打分,所有菜品从左到右排成一排,编号依次为 \(1∼n\),第 \(i\) 个菜品的美味值是 \(d_i\)。小猴可以自己选择对那些选手的菜品进行打分,但是必须满足以下两个条件:

① 至少选择两位选手的菜品;

② 选择的第一个选手和最后一位选手的菜品美味值必须相同。

小猴所选的第一个菜品和最后一个菜品之间(不包含第一个和最后一个菜品)的部分菜品可以选择不要,请你帮助小猴计算所选菜品美味值的总和的最大值是多少?

【输入】

第一行一个整数 \(n\)

第二行 \(n\) 个整数 \(d_1,d_2,\dots,d_n\)

【输出】

一行一个整数,表示所选菜品美味值的总和的最大值。

【输入样例】

5
1 2 3 1 2

【输出样例】

8

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数列 \(d_1, d_2, \ldots, d_n\),要求选择至少两个元素,且首尾元素值相同,中间元素可任意取舍,最大化所选元素之和。这是一个贪心累加 + 哈希映射问题,关键在于将问题转化为"以相同值为锚点的最大子段和",并用哈希表记录首次出现时的前缀和状态。

  2. 算法选择

    • 正数贪心累加:维护 sum 只累加正数元素,作为中间可选部分的最大贡献
    • 哈希映射首次出现f[v] 记录值 \(v\) 第一次出现时的 sum 状态(即该值作为首元素时,中间正数累加和)
    • 锚点配对:当值 \(v\) 再次出现时,计算以该值为首尾的最大子段和
  3. 关键步骤

    • 初始化
      • 读取 \(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] > 0sum += d[i](只累加正数,负数对中间段无贡献)
      • 记录首次出现
        • f[d_i + 10000] == -\inftyf[d_i + 10000] = sum(记录该值首次出现时的正数累加状态)
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),单次遍历数组
    • 空间复杂度:\(O(V)\)\(V\) 为值域大小(本题约 \(20000\)),哈希表 f
  5. 贪心正数累加的核心思想

    • 问题转化:所选子段 = 首元素 \(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\)
    • 至少两个元素的保证:配对时自然需要首尾两个相同值,满足条件①
    • 适用于"首尾相同值约束下的最大子段和"问题,贪心正数累加配合哈希映射可将复杂枚举优化为线性扫描

【解题思路】

【算法标签】

模拟

【代码详解】

#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
posted @ 2026-07-04 14:23  团爸讲算法  阅读(7)  评论(0)    收藏  举报