【luogu P6242】【模板】线段树 3(吉司机线段树)

【模板】线段树 3

题目链接:luogu P6242

题目大意

给你一个数组,然后要你维护一些东西:
区间加值,区间取最小值,区间求和,区间求最大值,区间求每个位置的历史最大值的最大值。

思路

这题的难点在于你操作二取最小值是无法用普通的线段树维护区间和的。

所以就要用到吉司机线段树。
它主要是通过维护第一大和第二大这两个东西,如果大于等于第一个就肯定无法更新,如果大于第二个就只会更新第一大,就弄就好了,否则就递归下去更新。


然后开始讲如何维护:

lsx,rsx:左右儿子
sumx:区间权值和
maxax:区间最大数
secx:区间第二大数
cntx:区间最大数的个数
maxbx:区间历史最大数
add_ax:区间最大值的加法的懒标记
add_a1x:区间的不是最大值的加法的懒标记(因为你要么递归下去搞,要么就搞最大值,所以可以这么维护)
add_bx,add_b1x:同理,不过这个是历史最大值和历史非最大值的懒标记。

然后你考虑怎么维护:

首先是上传: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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P6242.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2021-02-22 【luogu P3690】【模板】Link Cut Tree (动态树)
2021-02-22 【luogu P3384】【模板】轻重链剖分
2021-02-22 【ybt金牌导航5-1-1】【luogu P2590】树的统计
2021-02-22 【ybt金牌导航4-5-1】【luogu P3369】普通平衡树(替罪羊树做法)
2021-02-22 【ybt金牌导航4-4-1】【luogu P2596】维护书架 / 书架 / 无旋式treap模板
2021-02-22 【ybt金牌导航4-3-3】【luogu P2286】宠物收养所
2021-02-22 【ybt金牌导航4-1-1】【luogu P1552】派遣(左偏树模板)
点击右上角即可分享
微信分享提示