题解:AtCoder AT_awc0045_e Interval Evaluation Value
【题目来源】
AtCoder:E - Interval Evaluation Value
【题目描述】
Takahashi has an integer sequence \(A_1, A_2, \ldots, A_N\) of length \(N\).
高桥有一个长度为 \(N\) 的整数序列 \(A_1, A_2, \ldots, A_N\)。
Takahashi wants to select one contiguous interval of \(W\) elements from this sequence and maximize its evaluation score.
高桥希望从这个序列中选择一个包含 \(W\) 个元素的连续区间,并最大化其评价分数。
Specifically, he chooses an integer \(l\) (\(1 \leq l \leq N - W + 1\)) and defines the evaluation score of the interval \(A_l, A_{l+1}, \ldots, A_{l+W-1}\) by the following formula:
具体来说,他选择一个整数 \(l\)(\(1 \leq l \leq N - W + 1\)),并将区间 \(A_l, A_{l+1}, \ldots, A_{l+W-1}\) 的评价分数定义为以下公式:
\(\text{Evaluation Score} = \left(\sum_{i=l}^{l+W-1} A_i\right) + K \times \min(A_l, A_{l+1}, \ldots, A_{l+W-1})\)
That is, the evaluation score is the sum of the elements in the interval plus the product of the minimum value in the interval and the integer \(K\).
也就是说,评价分数是区间内元素之和加上区间最小值与整数 \(K\) 的乘积。
Note that \(K\) and each \(A_i\) can be positive, zero, or negative.
注意,\(K\) 和每个 \(A_i\) 可以是正数、零或负数。
Find the maximum evaluation score over all possible values of \(l\) (\(1 \leq l \leq N - W + 1\)).
求所有可能的 \(l\) 值(\(1 \leq l \leq N - W + 1\))中,评价分数的最大值。
【输入】
\(N\) \(W\) \(K\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
- The first line contains the integer \(N\) representing the length of the sequence, the integer \(W\) representing the length of the interval, and the integer \(K\) representing the coefficient multiplied by the minimum value, separated by spaces.
- The second line contains the integers \(A_1, A_2, \ldots, A_N\) representing the elements of the sequence, separated by spaces.
【输出】
Output the maximum evaluation score as an integer on a single line.
【输入样例】
5 3 2
1 3 2 5 4
【输出样例】
15
【核心思想】
-
问题分析:给定长度为 \(N\) 的序列 \(A\),选择长度为 \(W\) 的连续区间,最大化评价分数:区间和 \(+ K \times\) 区间最小值。这是一个单调栈 + 线段树问题,关键在于枚举每个元素作为区间最小值,并快速查询满足约束的最大区间和。
-
算法选择:
- 单调栈(Monotonic Stack):求每个元素左边第一个小于等于它的位置 \(L[i]\),右边第一个大于它的位置 \(R[i]\),确定以 \(A[i]\) 为最小值的合法区间范围
- 前缀和(Prefix Sum):快速计算任意长度为 \(W\) 的子数组和
- 线段树(Segment Tree):维护所有长度为 \(W\) 的区间的和,支持区间查询最大值和区间更新
- 懒标记(Lazy Propagation):支持高效的区间更新操作
-
关键步骤:
- 初始化:
- 读取 \(N\)(序列长度)、\(W\)(区间长度)、\(K\)(系数)
- 读取序列 \(A[1..N]\),计算前缀和
sa[i] = sa[i-1] + A[i]
- 单调栈求边界:
- 求 \(L[i]\):从左到右扫描,维护单调递增栈,\(L[i]\) 为左边第一个小于等于 \(A[i]\) 的位置(若不存在则为 \(0\))
- 求 \(R[i]\):从右到左扫描,维护单调递减栈,\(R[i]\) 为右边第一个大于 \(A[i]\) 的位置(若不存在则为 \(N+1\))
- 构建线段树:
- 叶子节点存储从位置 \(l\) 开始、长度为 \(W\) 的区间和:
sa[l+W-1] - sa[l-1] - 支持区间最大值查询和区间更新(懒标记)
- 叶子节点存储从位置 \(l\) 开始、长度为 \(W\) 的区间和:
- 枚举最小值位置:
- 遍历每个位置 \(i\)(\(1 \leq i \leq N\)),假设 \(A[i]\) 是区间的最小值
- 计算合法起始位置范围:
left = max(L[i] + 1, i - W + 1):起始位置必须大于 \(L[i]\)(确保 \(A[i]\) 是最小值),且区间必须包含 \(i\)right = min(i, R[i] - W):起始位置必须不超过 \(i\),且区间不能跨越 \(R[i]\)
- 跳过无效区间:若
left > right,则不存在以 \(A[i]\) 为最小值的长度为 \(W\) 的区间 - 查询最大区间和:
maxsum = query(1, left, right),线段树查询区间[left, right]中的最大区间和 - 更新答案:
ans = max(ans, maxsum + K * A[i])
- 输出答案:
ans
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),单调栈 \(O(N)\),线段树构建 \(O(N)\),每次查询 \(O(\log N)\),共 \(N\) 次查询
- 空间复杂度:\(O(N)\),前缀和数组、单调栈、线段树
-
单调栈 + 线段树的核心思想:
- 单调栈确定合法区间范围:以 \(A[i]\) 为最小值的区间必须满足:左边界 \(> L[i]\)(左边没有更小的元素),右边界 \(< R[i]\)(右边没有更小或相等的元素)。这确保了 \(A[i]\) 是区间的唯一最小值
- 线段树维护区间和:线段树的每个叶子节点代表一个长度为 \(W\) 的区间的和。通过区间查询,可以快速找到满足位置约束的最大区间和
- 位置约束的转化:对于以 \(A[i]\) 为最小值的区间,起始位置 \(l\) 必须满足:\(l \leq i \leq l+W-1\)(区间包含 \(i\)),\(l > L[i]\)(左边无更小元素),\(l+W-1 < R[i]\)(右边无更小元素)。转化为 \(l \in [\max(L[i]+1, i-W+1), \min(i, R[i]-W)]\)
- 枚举最小值求最优解:由于评价分数与区间最小值直接相关,枚举每个位置作为最小值,可以覆盖所有可能的区间情况,确保找到全局最优解
- 适用于"以某元素为极值的最大子数组和"、"带约束的区间最值"类问题
【解题思路】

【算法标签】
单调栈
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500005, INF = 1e18;
int n, w, k;
int a[N], sa[N];
int stk[N], tt;
int L[N], R[N];
int ans = -1e18;
// 线段树节点结构体
struct Node
{
int l, r; // 节点表示的区间范围
int dt, mx; // dt是懒标记,mx是区间最大值
}tr[N*4];
// 更新父节点信息
void pushup(int u)
{
tr[u].mx = max(tr[u<<1].mx, tr[u<<1|1].mx);
}
// 下传懒标记
void pushdown(int u)
{
auto &root = tr[u], &l = tr[u<<1], &r = tr[u<<1|1];
l.dt += root.dt, l.mx += root.dt;
r.dt += root.dt, r.mx += root.dt;
root.dt = 0;
}
// 构建线段树
void build(int u, int l, int r)
{
if (l == r) // 叶子节点
{
tr[u] = {l, r, 0, sa[l+w-1] - sa[l-1]}; // 存储从l开始,长度为w的区间和
}
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
// 区间更新
void update(int u, int l, int r, int d)
{
if (tr[u].l >= l && tr[u].r <= r) // 完全包含
{
tr[u].dt += d, tr[u].mx += d; // 更新当前节点的懒标记和最大值
}
else
{
pushdown(u); // 下传懒标记
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u<<1, l, r, d);
if (r > mid) update(u<<1|1, l, r, d);
pushup(u);
}
}
// 区间查询最大值
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) // 树中节点,已经被完全包含在[l,r]中了
{
return tr[u].mx;
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int res = -INF; // 初始化为负无穷
if (l <= mid) res = query(u<<1, l, r);
if (r > mid) res = max(res, query(u<<1|1, l, r));
return res;
}
}
signed main()
{
cin >> n >> w >> k;
// 读取数据并计算前缀和
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sa[i] = sa[i-1] + a[i];
}
// 单调栈求每个元素左边第一个小于等于它的位置
tt = 0;
for (int i = 1; i <= n; i++)
{
// 维护单调递增栈,弹出大于等于当前元素的栈顶
while (tt && a[stk[tt]] >= a[i]) tt--;
if (tt)
L[i] = stk[tt]; // 左边第一个小于当前元素的位置
else
L[i] = 0; // 左边没有更小的元素
stk[++tt] = i;
}
// 单调栈求每个元素右边第一个大于它的位置
tt = 0;
for (int i = n; i >= 1; i--)
{
// 维护单调递减栈,弹出小于等于当前元素的栈顶
while (tt && a[stk[tt]] > a[i]) tt--;
if (tt)
R[i] = stk[tt]; // 右边第一个大于当前元素的位置
else
R[i] = n + 1; // 右边没有更大的元素
stk[++tt] = i;
}
// 构建线段树,存储所有长度为w的区间的和
build(1, 1, n - w + 1);
// 遍历每个位置i
for (int i = 1; i <= n; i++)
{
// 计算包含i的长度为w的区间的可能起始位置范围
// left: 起始位置的下界
int left = max(L[i] + 1, i - w + 1);
// right: 起始位置的上界
int right = min(i, R[i] - w);
// 如果没有有效的区间,跳过
if (left > right) continue;
// 查询区间[left, right]中的最大区间和
int maxsum = query(1, left, right);
// 更新答案:最大区间和 + k * 当前元素值
ans = max(ans, maxsum + k * a[i]);
}
cout << ans << endl;
return 0;
}
【运行结果】
5 3 2
1 3 2 5 4
15
浙公网安备 33010602011771号