势能线段树
感觉这个科技挺有用,能解决一些常用而又阴间的操作,所以就写一下(
模板:给你一个长度为 \(n\) 序列,\(m\) 次操作,有三种操作:
0 l r x
: 对于 \(i\in [l,r]\) 执行 \(a_i=\min (a_i, x)\)。
1 l r
:输出 \(\max(a_l,a_{l+1}...a_r)\)。
2 l r
:输出 \(\sum\limits^r_{i=l}a_i\)。
\(1\le n,m\le 2\times 10^5\)。
其实这道题可以不用势能线段树,我一来想的是一个分块的做法:
取块长 \(\sqrt{n}\),对于每一块,我们维护原序列这个块内的元素排序后的结果。
修改时散块暴力重构,复杂度 \(O(\sqrt{n})\),修改整块时还是暴力重构,从大往小扫到 \(a_i\le t\) 为止,因为序列只会减小不会增加,这样复杂度 \(O(\sqrt{n})\)。
总的复杂度 \(O(m\sqrt{n})\),稳过校内OJ过于老年估计跑不了。
但这个玩意儿但凡我们区间修改加个 \(\max\) 操作它就 \(O(m\sqrt{n\log n})\) 了(整块要二分而不能暴力了),这就不好,所以我们使用势能线段树。
后两个操作是线段树的常规操作,而修改操作对区间和的影响不太好直接计算。
对于线段树每个节点,我们维护 \(mx,mxcnt,secmx,sum\) 分别表示最大值,最大值出现次数,次大值,区间和。
每次修改到当前节点,且修改区间完全包含当前节点管辖的区间,则分为三种情况:
- \(mx\le x\),直接不管跑路。
- \(secmx<x\),当前节点 \(sum -= mxcnt\times (mx-x)\),打个懒标记。
- \(x\le secmx\),递归进入左右子树继续修改。
那么到此时,我们就得到了一个至少正确性没有问题的算法。
但修改操作的第三种情况,不会拖累全局的复杂度吗?
考虑势能分析。设当前势能为这颗线段树中,每个节点管辖区间中不同的数字个数总和。开始的时候,势能为 \(O(n\log n)\)。并且修改操作显然最多让势能 \(+1\)。
每当我们处于第三种情况,继续递归进入左右子树时,势能必定减少 \(1\)。而当势能 \(=1\) 时,不论哪个节点必定没有次大值,也就不会出现 \(x\le secmx\) 的情况。
即复杂度均摊下来是严格 \(O(n\log n+m\log n)\) 的。
#include <cstdio>
#define int long long
inline int max(const int x, const int y) {return x > y ? x : y;}
struct Node {
int l, r, mx, mxcnt, semx, sum, lazy;
} tree[800005];
int a[200005];
inline void pushup(int O) {
int v[] = {tree[O << 1].mx, tree[O << 1].semx, tree[O << 1 | 1].mx, tree[O << 1 | 1].semx};
int c[] = {tree[O << 1].mxcnt, 0, tree[O << 1 | 1].mxcnt, 0};
tree[O].mx = max(v[0], v[2]);
tree[O].mxcnt = 0;
if (v[0] == tree[O].mx) tree[O].mxcnt += c[0], v[0] = 0;
if (v[2] == tree[O].mx) tree[O].mxcnt += c[2], v[2] = 0;
tree[O].semx = max(v[0], max(v[1], max(v[2], v[3])));
tree[O].sum = tree[O << 1].sum + tree[O << 1 | 1].sum;
}
inline void pushdown(int O) {
if (tree[O].lazy < tree[O << 1].mx) {
tree[O << 1].sum -= tree[O << 1].mxcnt * (tree[O << 1].mx - tree[O].lazy);
tree[O << 1].mx = tree[O].lazy;
tree[O << 1].lazy = tree[O].lazy;
}
if (tree[O].lazy < tree[O << 1 | 1].mx) {
tree[O << 1 | 1].sum -= tree[O << 1 | 1].mxcnt * (tree[O << 1 | 1].mx - tree[O].lazy);
tree[O << 1 | 1].mx = tree[O].lazy;
tree[O << 1 | 1].lazy = tree[O].lazy;
}
tree[O].lazy = 1e18;
}
void build(int O, int L, int R) {
tree[O].l = L, tree[O].r = R, tree[O].lazy = 1e18;
if (L != R) {
build(O << 1, L, L + R >> 1);
build(O << 1 | 1, (L + R >> 1) + 1, R);
pushup(O);
} else tree[O].mx = tree[O].sum = a[L], tree[O].mxcnt = 1;
}
void update(int O, int L, int R, int x) {
if (L <= tree[O].l && tree[O].r <= R) {
if (tree[O].mx <= x) return;
if (tree[O].semx < x) {
tree[O].sum -= tree[O].mxcnt * (tree[O].mx - x), tree[O].mx = tree[O].lazy = x;
return;
}
pushdown(O);
update(O << 1, L, R, x);
update(O << 1 | 1, L, R, x);
pushup(O);
return;
}
int mid = tree[O].l + tree[O].r >> 1;
pushdown(O);
if (L <= mid) update(O << 1, L, R, x);
if (mid < R) update(O << 1 | 1, L, R, x);
pushup(O);
}
int getmax(int O, int L, int R) {
if (L <= tree[O].l && tree[O].r <= R) return tree[O].mx;
pushdown(O);
int mid = tree[O].l + tree[O].r >> 1, ans = 0;
if (L <= mid) ans = getmax(O << 1, L, R);
if (mid < R) ans = max(ans, getmax(O << 1 | 1, L, R));
return ans;
}
int getsum(int O, int L, int R) {
if (L <= tree[O].l && tree[O].r <= R) return tree[O].sum;
pushdown(O);
int mid = tree[O].l + tree[O].r >> 1, ans = 0;
if (L <= mid) ans = getsum(O << 1, L, R);
if (mid < R) ans += getsum(O << 1 | 1, L, R);
return ans;
}
signed main() {
int n, m;
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; ++ i) scanf("%lld", a + i);
build(1, 1, n);
while (m --) {
int opt, l, r, v;
scanf("%lld%lld%lld", &opt, &l, &r);
if (opt == 0) {scanf("%lld", &v); if (v) update(1, l, r, v);}
else if (opt == 1) printf("%lld\n", getmax(1, l, r));
else printf("%lld\n", getsum(1, l, r));
}
return 0;
}