题解:洛谷 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
【核心思想】
-
问题分析:给定长度为 \(N\) 的三个序列 \(A, B, C\),需要选择两个分割点 \(x, y\)(满足 \(1 \le x < y < N\)),将序列划分为三段:\([1, x]\) 取 \(A\)、\([x+1, y]\) 取 \(B\)、\([y+1, N]\) 取 \(C\),最大化三段的和。这是一个前缀和 + 后缀最大值预处理问题,关键在于将二维枚举优化为线性扫描。
-
算法选择:
- 前缀和(Prefix Sum):快速计算任意区间的和,将区间求和转化为 \(O(1)\) 的差分计算
- 后缀最大值数组(Suffix Maximum):预处理对于每个 \(y\) 起点,最优的 \((y, C\text{后缀})\) 组合值,将内层枚举降为 \(O(1)\) 查询
-
关键步骤:
- 前缀和初始化:计算 \(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),前缀和 \(O(N)\) + 后缀预处理 \(O(N)\) + 枚举 \(x\) 为 \(O(N)\)
- 空间复杂度:\(O(N)\),三个前缀和数组 + 一个后缀最大值数组
-
前缀和与后缀最大值的核心思想:
- 区间求和降维:前缀和将 \(\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
浙公网安备 33010602011771号