洛谷P6792 [SNOI2020] 区间和 题解 吉司机线段树
题目链接:https://www.luogu.com.cn/problem/P6792
这题可以用维护线性函数转折点的势能线段树解决。
普通的“最小值、次小值、最小值个数”还不够:即使修改值没有达到次小值,最大子段对应的区间也可能发生变化。
思路
1. 把维护的信息看成线性函数
线段树维护:
sum:区间和;pre:最大前缀和;suf:最大后缀和;ans:最大子段和。
后三者允许取空区间。
设当前节点的最小值为 \(mn\)。如果只把最小值增加 \(d\),其他元素不变,那么一个固定子区间的和会增加
\[d\times cnt,
\]
其中 \(cnt\) 是这个子区间中最小值的个数。
因此,对于上述四项信息,除了当前值,还维护它对应的最小值个数。
但这些信息只能在“最优区间不发生变化”时直接更新,所以还需要维护一个阈值 lim:
当 \(mn\le x<lim\) 时,可以直接按照记录的最小值个数更新。
2. 计算转折点
最大前缀和、最大后缀和、最大子段和的合并公式分别是:
\[pre=\max(pre_l,sum_l+pre_r),
\]
\[suf=\max(suf_r,suf_l+sum_r),
\]
\[ans=\max(ans_l,ans_r,suf_l+pre_r).
\]
它们都是若干线性函数的最大值。
假设当前胜出的函数为
\[v_1+c_1d,
\]
另一个候选为
\[v_2+c_2d,
\]
其中 \(v_1\ge v_2\)。
- 若 \(c_2\le c_1\),后者不会反超。
- 若 \(c_2>c_1\),后者第一次严格反超时,整数增量为
\[d=\left\lfloor\frac{v_1-v_2}{c_2-c_1}\right\rfloor+1.
\]
于是用 \(mn+d\) 更新 lim。
同时,lim 还要受到子节点有效范围的限制:
- 子节点最小值等于当前最小值,继承其
lim; - 否则,不能直接修改到该子节点的最小值,因为此时最小值的组成会改变。
这样也包含了普通线段树 Beats 中次小值的限制。
3. 修改和查询
修改时:
- 若 \(x\le mn\),直接返回;
- 若完整覆盖且 \(x<lim\),直接更新四个值;
- 否则下传并递归,最后重新合并。
查询按照普通最大子段和线段树处理,单次复杂度为 \(O(\log n)\),空间复杂度为 \(O(n)\)。
样例的四次查询分别得到 6、7、10、11。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 1e5 + 5, inf = 2e9;
struct Line {
ll v;
int cnt;
bool operator<(const Line &a) const {
return v == a.v ? cnt < a.cnt : v < a.v;
}
};
Line operator+(const Line &a, const Line &b) {
return {a.v + b.v, a.cnt + b.cnt};
}
struct Node {
Line sum, pre, suf, ans;
int mn, lim;
} tr[maxn << 2];
struct Info {
ll sum, pre, suf, ans;
};
int n, q, a[maxn];
void check(Node &t, Line a, Line b) {
if (a.cnt >= b.cnt) return;
ll d = b.cnt - a.cnt, v = a.v - b.v;
if (d * (t.lim - (ll)t.mn - 1) <= v) return;
t.lim = min((ll)t.lim, t.mn + v / d + 1);
}
void push_up(int u) {
Node &t = tr[u];
Node a = tr[u << 1], b = tr[u << 1 | 1];
t.mn = min(a.mn, b.mn);
t.lim = min(a.mn == t.mn ? a.lim : a.mn,
b.mn == t.mn ? b.lim : b.mn);
if (a.mn != t.mn)
a.sum.cnt = a.pre.cnt = a.suf.cnt = a.ans.cnt = 0;
if (b.mn != t.mn)
b.sum.cnt = b.pre.cnt = b.suf.cnt = b.ans.cnt = 0;
Line pre = a.sum + b.pre;
Line suf = a.suf + b.sum;
Line ans = a.suf + b.pre;
t.sum = a.sum + b.sum;
t.pre = max(a.pre, pre);
t.suf = max(b.suf, suf);
t.ans = max(max(a.ans, b.ans), ans);
check(t, t.pre, a.pre);
check(t, t.pre, pre);
check(t, t.suf, b.suf);
check(t, t.suf, suf);
check(t, t.ans, a.ans);
check(t, t.ans, b.ans);
check(t, t.ans, ans);
}
void set_val(int u, int x) {
tr[u].mn = x;
tr[u].lim = x < 0 ? 0 : inf;
tr[u].sum = {x, 1};
tr[u].pre = tr[u].suf = tr[u].ans = {max(0, x), x >= 0};
}
void change(int u, int x) {
ll d = (ll)x - tr[u].mn;
tr[u].sum.v += d * tr[u].sum.cnt;
tr[u].pre.v += d * tr[u].pre.cnt;
tr[u].suf.v += d * tr[u].suf.cnt;
tr[u].ans.v += d * tr[u].ans.cnt;
tr[u].mn = x;
}
void push_down(int u) {
int x = tr[u].mn;
if (tr[u << 1].mn < x) change(u << 1, x);
if (tr[u << 1 | 1].mn < x) change(u << 1 | 1, x);
}
void build(int u, int l, int r) {
if (l == r) {
set_val(u, a[l]);
return;
}
int mid = (l + r) / 2;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
push_up(u);
}
void modify(int u, int l, int r, int ql, int qr, int x) {
if (tr[u].mn >= x) return;
if (l == r) {
set_val(u, x);
return;
}
if (ql <= l && r <= qr && x < tr[u].lim) {
change(u, x);
return;
}
push_down(u);
int mid = (l + r) / 2;
if (ql <= mid) modify(u << 1, l, mid, ql, qr, x);
if (qr > mid) modify(u << 1 | 1, mid + 1, r, ql, qr, x);
push_up(u);
}
Info merge(Info a, Info b) {
return {
a.sum + b.sum,
max(a.pre, a.sum + b.pre),
max(b.suf, a.suf + b.sum),
max(max(a.ans, b.ans), a.suf + b.pre)
};
}
Info query(int u, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return {
tr[u].sum.v,
tr[u].pre.v,
tr[u].suf.v,
tr[u].ans.v
};
}
push_down(u);
int mid = (l + r) / 2;
if (qr <= mid) return query(u << 1, l, mid, ql, qr);
if (ql > mid) return query(u << 1 | 1, mid + 1, r, ql, qr);
return merge(query(u << 1, l, mid, ql, qr),
query(u << 1 | 1, mid + 1, r, ql, qr));
}
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
build(1, 1, n);
for (int i = 1, op, l, r, x; i <= q; i++) {
scanf("%d%d%d", &op, &l, &r);
if (op == 0) {
scanf("%d", &x);
modify(1, 1, n, l, r, x);
}
else {
printf("%lld\n", query(1, 1, n, l, r).ans);
}
}
return 0;
}
浙公网安备 33010602011771号