题解:洛谷 AT_abc438_d Tail of Snake

【题目来源】

洛谷:AT_abc438_d Tail of Snake - 洛谷

【题目描述】

Snuke 正在观察一条蛇,他很好奇蛇的头部、蛇身和蛇尾分别是哪个部分。他把蛇分成了 \(N\) 块,并评估了每个块的头部相似度、身体相似度和尾部相似度。然后,他决定找出使相似值总和最大的分割方法。

给你长度为 \(N\) 的整数序列 \(A = (A_1, A_2, \ldots, A_N)\)\(B = (B_1, B_2, \ldots, B_N)\)\(C = (C_1, C_2, \ldots, C_N)\)

求满足 \(1 \le x < y < N\) 的一对整数 \((x, y)\) ,最大化 \(\displaystyle\sum_{i = 1}^{x} A_i + \sum_{i = x + 1}^{y} B_i + \sum_{i = y + 1}^{N} C_i\) 的值。

【输入】

输入内容由标准输入法提供,格式如下

\(N\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
\(B_1\) \(B_2\) \(\ldots\) \(B_N\)
\(C_1\) \(C_2\) \(\ldots\) \(C_N\)

【输出】

输出答案。

【输入样例】

5
1 4 2 4 3
2 3 4 2 2
3 2 4 4 3

【输出样例】

16

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的三个序列 \(A, B, C\),需要选择两个分割点 \(x, y\)(满足 \(1 \le x < y < N\)),将序列划分为三段:\([1, x]\)\(A\)\([x+1, y]\)\(B\)\([y+1, N]\)\(C\),最大化三段的和。这是一个前缀和 + 后缀最大值预处理问题,关键在于将二维枚举优化为线性扫描。

  2. 算法选择

    • 前缀和(Prefix Sum):快速计算任意区间的和,将区间求和转化为 \(O(1)\) 的差分计算
    • 后缀最大值数组(Suffix Maximum):预处理对于每个 \(y\) 起点,最优的 \((y, C\text{后缀})\) 组合值,将内层枚举降为 \(O(1)\) 查询
  3. 关键步骤

    • 前缀和初始化:计算 \(sa[i] = \sum_{j=1}^{i} A_j\)\(sb[i] = \sum_{j=1}^{i} B_j\)\(sc[i] = \sum_{j=1}^{i} C_j\),时间 \(O(N)\)
    • 后缀最大值预处理(从后往前扫描):
      • 定义 \(tmp[i] = \max_{j \ge i} \left( sb[j] + (sc[N] - sc[j]) \right)\),表示从位置 \(i\) 开始选择 \(y\) 时,\(B\) 段与 \(C\) 段之和的最大值
      • 递推式:\(tmp[i] = \max(tmp[i+1],\ sb[i] + sc[N] - sc[i])\)
      • \(i = N-1\) 递减到 \(2\) 填充 \(tmp\) 数组
    • 枚举分割点 \(x\)
      • 固定 \(x\) 后,\(y\) 的最优选择已由 \(tmp[x+1]\) 给出
      • 总值为 \(sa[x] + tmp[x+1] - sb[x]\)(减去 \(sb[x]\) 是因为 \(tmp\) 包含 \(sb[y]\) 而非 \(sb[y] - sb[x]\)
      • 遍历 \(x\)\(1\)\(N-1\),取最大值更新 \(ans\)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),前缀和 \(O(N)\) + 后缀预处理 \(O(N)\) + 枚举 \(x\)\(O(N)\)
    • 空间复杂度:\(O(N)\),三个前缀和数组 + 一个后缀最大值数组
  5. 前缀和与后缀最大值的核心思想

    • 区间求和降维:前缀和将 \(\sum_{i=l}^{r} a_i\) 转化为 \(sa[r] - sa[l-1]\),实现 \(O(1)\) 区间查询
    • 分治式优化:将原式拆为 \(A\) 前缀 + \((B\text{段}+C\text{后缀})\),后者与 \(x\) 无关,可独立预处理
    • 后缀最大值递推\(tmp[i] = \max(tmp[i+1],\ \text{当前值})\),利用已计算的 \(tmp[i+1]\) 避免重复枚举
    • 双指针思想的离散化:固定 \(x\)\(y\) 的最优解具有单调性,预处理后将 \(O(N^2)\) 降为 \(O(N)\)
    • 适用于"序列分割 + 多段求和最大化"类问题,特别是各段使用不同权值函数的场景

【算法标签】

普及 #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 定义int为long long,防止溢出

const int N = 300005;
int n;  // 天数
int a[N], b[N], c[N];  // 每天的三种选择
int sa[N], sb[N], sc[N];  // 前缀和数组
int tmp[N];  // 后缀最大值数组

signed main()  // signed是int的别名,因为定义了#define int long long
{
    // 输入天数
    cin >> n;

    // 输入a数组并计算前缀和sa
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sa[i] = sa[i - 1] + a[i];  // sa[i] = a[1]到a[i]的和
    }

    // 输入b数组并计算前缀和sb
    for (int i = 1; i <= n; i++)
    {
        cin >> b[i];
        sb[i] = sb[i - 1] + b[i];  // sb[i] = b[1]到b[i]的和
    }

    // 输入c数组并计算前缀和sc
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
        sc[i] = sc[i - 1] + c[i];  // sc[i] = c[1]到c[i]的和
    }

    // 预处理tmp数组:从后往前计算后缀最大值
    // tmp[i] = max_{j>=i} (sb[j] + (sc[n] - sc[j]))
    for (int i = n - 1; i > 1; i--)
    {
        // 比较当前值sb[i] + 从i+1到n的c的和 与 后面的最大值tmp[i+1]
        tmp[i] = max(tmp[i + 1], sb[i] + sc[n] - sc[i]);
    }

    // 输出调试信息(注释部分)
    // for (int i=1; i<=n; i++)
    //     cout << tmp[i] << " ";
    // cout << endl;
    // for (int i=2; i<n; i++)
    //     cout << tmp[i] << " ";
    // cout << endl;

    // 计算最终答案
    int ans = -1;
    for (int i = 1; i < n; i++)
    {
        // 公式:sa[i] + tmp[i+1] - sb[i]
        ans = max(ans, sa[i] + tmp[i + 1] - sb[i]);
    }

    cout << ans << endl;
    return 0;
}

【运行结果】

5
1 4 2 4 3
2 3 4 2 2
3 2 4 4 3
16
posted @ 2026-08-27 11:04  团爸讲算法  阅读(6)  评论(0)    收藏  举报