【luogu P6242】【模板】线段树 3(吉司机线段树)
【模板】线段树 3
题目链接:luogu P6242
题目大意
给你一个数组,然后要你维护一些东西:
区间加值,区间取最小值,区间求和,区间求最大值,区间求每个位置的历史最大值的最大值。
思路
这题的难点在于你操作二取最小值是无法用普通的线段树维护区间和的。
所以就要用到吉司机线段树。
它主要是通过维护第一大和第二大这两个东西,如果大于等于第一个就肯定无法更新,如果大于第二个就只会更新第一大,就弄就好了,否则就递归下去更新。
然后开始讲如何维护:
\(ls_x,rs_x\):左右儿子
\(sum_x\):区间权值和
\(maxa_x\):区间最大数
\(sec_x\):区间第二大数
\(cnt_x\):区间最大数的个数
\(maxb_x\):区间历史最大数
\(add\_a_x\):区间最大值的加法的懒标记
\(add\_a1_x\):区间的不是最大值的加法的懒标记(因为你要么递归下去搞,要么就搞最大值,所以可以这么维护)
\(add\_b_x,add\_b1_x\):同理,不过这个是历史最大值和历史非最大值的懒标记。
然后你考虑怎么维护:
首先是上传:\(maxa,maxb\) 直接取最大值,\(sum\) 直接加起来,\(sec,cnt\) 稍微复杂一点就分类讨论一下即可。
然后是下传:考虑那边是最大值就把下传的最大给它的最大,否则它的最大是这个的非最大(两边的非最大自然都是非最大)
至于怎么给,就是这个线段树的一个难点了。
首先全部加上最大加的乘最大的个数和非最大加的和非最大的个数(非最大的个数用全部减去最大个数)。
然后历史最大要加的加上历史要加的,懒标记也要加上各自的,然后反正就是各自加各自的,具体可以看看这段代码:
void update(int now, int k1, int k2, int k3, int k4) {//k1:最大要加的 k2:历史最大要加的 k3:非最大要加的 k4:历史非最大要加的
sum[now] += 1ll * k1 * cnt[now] + 1ll * k3 * (rs[now] - ls[now] + 1 - cnt[now]);
maxb[now] = max(maxb[now], maxa[now] + k2);//历史最大值加上历史要加的
add_b[now] = max(add_b[now], add_a[now] + k2);//懒标记也记得加
add_b1[now] = max(add_b1[now], add_a1[now] + k4);//非最大的懒标记也要有
maxa[now] += k1; add_a[now] += k1;
if (sec[now] != 1e9) sec[now] += k3; add_a1[now] += k3;
}
然后别的操作都比较好搞了。
代码
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
int re, zf; char c;
int read() {
re = 0; zf = 1;
c = getchar(); while (c < '0' || c > '9') {if (c == '-') zf = -zf; c = getchar();}
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return zf * re;
}
void writee(ll x) {
if (x > 9) writee(x / 10), x %= 10;
putchar(x + '0');
}
void write(ll x) {
if (x < 0) putchar('-'), x = -x;
writee(x);
putchar('\n');
}
const int N = 500001;
int n, m, a[N], op, l, r, x;
struct XD_tree {
ll sum[N << 2];
int add_a[N << 2], add_b[N << 2], add_a1[N << 2], add_b1[N << 2];
int ls[N << 2], rs[N << 2], maxa[N << 2], sec[N << 2], maxb[N << 2], cnt[N << 2];
void up(int now) {
maxa[now] = max(maxa[now << 1], maxa[now << 1 | 1]);
maxb[now] = max(maxb[now << 1], maxb[now << 1 | 1]);
sum[now] = sum[now << 1] + sum[now << 1 | 1];
if (maxa[now << 1] == maxa[now << 1 | 1]) {//小小分类讨论
sec[now] = max(sec[now << 1], sec[now << 1 | 1]);
cnt[now] = cnt[now << 1] + cnt[now << 1 | 1];
}
else if (maxa[now << 1] > maxa[now << 1 | 1]) {
sec[now] = max(sec[now << 1], maxa[now << 1 | 1]);
cnt[now] = cnt[now << 1];
}
else {
sec[now] = max(maxa[now << 1], sec[now << 1 | 1]);
cnt[now] = cnt[now << 1 | 1];
}
}
void update(int now, int k1, int k2, int k3, int k4) {//k1:最大要加的 k2:历史最大要加的 k3:非最大要加的 k4:历史非最大要加的
sum[now] += 1ll * k1 * cnt[now] + 1ll * k3 * (rs[now] - ls[now] + 1 - cnt[now]);
maxb[now] = max(maxb[now], maxa[now] + k2);//历史最大值加上历史要加的
add_b[now] = max(add_b[now], add_a[now] + k2);//懒标记也记得加
add_b1[now] = max(add_b1[now], add_a1[now] + k4);//非最大的懒标记也要有
maxa[now] += k1; add_a[now] += k1;
if (sec[now] != 1e9) sec[now] += k3; add_a1[now] += k3;
}
void down(int now) {
int x = max(maxa[now << 1], maxa[now << 1 | 1]);//这里不能用 maxa[now],因为你上面打标记的时候就加上了新加的值
if (maxa[now << 1] == x) update(now << 1, add_a[now], add_b[now], add_a1[now], add_b1[now]);
else update(now << 1, add_a1[now], add_b1[now], add_a1[now], add_b1[now]);
if (maxa[now << 1 | 1] == x) update(now << 1 | 1, add_a[now], add_b[now], add_a1[now], add_b1[now]);
else update(now << 1 | 1, add_a1[now], add_b1[now], add_a1[now], add_b1[now]);
add_a[now] = add_b[now] = add_a1[now] = add_b1[now] = 0;
}
void build(int now, int l, int r) {
ls[now] = l; rs[now] = r;
if (l == r) {
sum[now] = maxa[now] = maxb[now] = a[l];
sec[now] = -1e9; cnt[now] = 1;
return ;
}
int mid = (l + r) >> 1; build(now << 1, l, mid); build(now << 1 | 1, mid + 1, r);
up(now);
}
void update_add(int now) {
if (ls[now] > r || rs[now] < l) return ;
if (l <= ls[now] && rs[now] <= r) {
update(now, x, x, x, x); return ;
}
down(now);
update_add(now << 1); update_add(now << 1 | 1);
up(now);
}
void update_min(int now) {
if (ls[now] > r || rs[now] < l || x >= maxa[now]) return ;//如果大于了最大,那一定不会更新
if (l <= ls[now] && rs[now] <= r && x > sec[now]) {//如果大于第二大,那肯定是只有最大值会改
update(now, x - maxa[now], x - maxa[now], 0, 0); return ;//相当于加负数补回去
}
down(now);//否则就继续递归下去
update_min(now << 1); update_min(now << 1 | 1);
up(now);
}
ll get_sum(int now) {
if (ls[now] > r || rs[now] < l) return 0;
if (l <= ls[now] && rs[now] <= r) return sum[now];
down(now);
return get_sum(now << 1) + get_sum(now << 1 | 1);
}
int get_maxa(int now) {
if (ls[now] > r || rs[now] < l) return -1e9;
if (l <= ls[now] && rs[now] <= r) return maxa[now];
down(now); return max(get_maxa(now << 1), get_maxa(now << 1 | 1));
}
int get_maxb(int now) {
if (ls[now] > r || rs[now] < l) return -1e9;
if (l <= ls[now] && rs[now] <= r) return maxb[now];
down(now); return max(get_maxb(now << 1), get_maxb(now << 1 | 1));
}
}T;
int main() {
n = read(); m = read();
for (int i = 1; i <= n; i++) a[i] = read();
T.build(1, 1, n);
while (m--) {
op = read(); l = read(); r = read();
if (op == 1) x = read(), T.update_add(1);
if (op == 2) x = read(), T.update_min(1);
if (op == 3) write(T.get_sum(1));
if (op == 4) write(T.get_maxa(1));
if (op == 5) write(T.get_maxb(1));
}
return 0;
}