线段树进阶学习笔记
线段树是一种常见的数据结构,除了基础用法,我们还需要掌握一些进阶。
线段树的合并与分裂
概念
线段树的合并通常是针对权值线段树的合并,将两棵线段树的信息合并起来保存在一棵线段树中。为了节省空间,我们通常将两棵线段树的信息直接保留在一棵线段树中。需要留意的是,初始节点个数为
int merge(int p, int q, int l, int r) {
if (!p || !q) return p + q;
if (l == r) return sm(p) += sm(q), p;
int mid = (l + r) >> 1;
lc(p) = merge(lc(p), lc(q), l, mid);
rc(p) = merge(rc(p), rc(q), mid + 1, r);
push_up(p);
return p;
}
线段树的分裂则通常是将权值线段树前
void split(int p, int &q, ll k) {
if (!p) return;
q = nw();
ll num = sm(lc(p));
if (k > num) split(rc(p), rc(q), k - num);
else swap(rc(p), rc(q));
if (k < num) split(lc(p), lc(q), k);
sm(q) = sm(p) - k;
sm(p) = k;
}
应用
线段树合并/分裂通常用来解决一类集合动态变化时权值的维护问题。其中线段树合并的应用范围较广,而线段树分裂常和 ODT 结合,来维护 ODT 的分裂操作。
线段树优化建图
当我们需要将一个点连向一个区间时,暴力连边的复杂度来到了
线段树分治
概念
线段树分治是离线维护带撤销问题的一种思想,常用于维护“某操作的有效时间是
void dfs(int p) {
for (auto i : v(p)) add(i); // 增加贡献
if (l(p) == r(p)) ans[l(p)] = ask(); // 记录答案
else dfs(lc), dfs(rc);
del(); // 撤销贡献
}
应用
由于线段树分治维护答案的可撤销性,其通常于可撤销并查集结合,维护动态加删边的连通性问题。例如动态加删边的二分图判定问题,我们采用线段树分治结合扩展域可撤销并查集来解决问题。同时对于一些规模较小的数据结构,如线性基,我们可以在
线段树二分
如果线段树维护的信息具有单调性,当我们固定了区间的右端点
值得一提的是,线段树求解
这里给出代码框架:
int fnd(int p) {
if (l(p) == r(p)) return l(p);
if (/*左子树满足条件*/) return fnd(lc);
return fnd(rc);
}
吉司机线段树
吉司机线段树通常用来处理区间最值操作 & 区间历史最值问题。
区间最值
不带区间加减
给定长度为
对于区间取
- 若
,直接返回; - 若
, ,并将当前区间的最大值改为 ; - 若
,无法直接维护,需要继续向下递归。
通过势能分析的方法可以知道这样操作的时间复杂度是
带区间加减
直接维护难以确定标记的次序。然而注意到区间取最大值的操作只会改变
这样一来我们维护的标记有最大值,严格次大值,最小值,严格次小值,最大值个数,最小值个数,区间和,最大值的加法标记,最小值的加法标记,其它值的加法标记。
需要留意的是下传标记时要判断下传的子区间最大值是否是当前区间的最大值,若不是则应当下传其它值的标记。以及需要判断一个数既是最大值又是最小值时做贡献的情形。
给出代码:
#include <bits/stdc++.h>
#define N 500005
#define int long long
using namespace std;
const int inf = 1e15;
int n, m;
int a[N];
struct Node {
int l, r, sm;
int mx, nx, cx;
int mn, nn, cn;
int tgx, tgn, tgo;
} e[N << 2];
#define l(i) e[i].l
#define r(i) e[i].r
#define sm(i) e[i].sm
#define mx(i) e[i].mx
#define nx(i) e[i].nx
#define cx(i) e[i].cx
#define mn(i) e[i].mn
#define nn(i) e[i].nn
#define cn(i) e[i].cn
#define tgx(i) e[i].tgx
#define tgn(i) e[i].tgn
#define tgo(i) e[i].tgo
#define lc (p << 1)
#define rc (lc | 1)
void psup(int p) {
sm(p) = sm(lc) + sm(rc);
if (mx(lc) > mx(rc)) {
mx(p) = mx(lc);
nx(p) = max(nx(lc), mx(rc));
cx(p) = cx(lc);
}
else if (mx(lc) < mx(rc)) {
mx(p) = mx(rc);
nx(p) = max(mx(lc), nx(rc));
cx(p) = cx(rc);
}
else {
mx(p) = mx(lc);
nx(p) = max(nx(lc), nx(rc));
cx(p) = cx(lc) + cx(rc);
}
if (mn(lc) < mn(rc)) {
mn(p) = mn(lc);
nn(p) = min(nn(lc), mn(rc));
cn(p) = cn(lc);
}
else if (mn(lc) > mn(rc)) {
mn(p) = mn(rc);
nn(p) = min(mn(lc), nn(rc));
cn(p) = cn(rc);
}
else {
mn(p) = mn(lc);
nn(p) = min(nn(lc), nn(rc));
cn(p) = cn(lc) + cn(rc);
}
}
void bud(int p, int l, int r) {
l(p) = l, r(p) = r;
if (l == r) {
sm(p) = mx(p) = mn(p) = a[l];
cx(p) = cn(p) = 1;
nx(p) = -inf, nn(p) = inf;
return;
}
int mid = (l + r) >> 1;
bud(lc, l, mid);
bud(rc, mid + 1, r);
psup(p);
}
void tgdn(int p, int tgx, int tgn, int tgo) {
if (mx(p) == mn(p)) {
if (tgx == tgo) tgx = tgn;
else tgn = tgx;
sm(p) += cx(p) * tgx;
}
else sm(p) += cx(p) * tgx + cn(p) * tgn + (r(p) - l(p) + 1 - cx(p) - cn(p)) * tgo;
if (nx(p) == mn(p)) nx(p) += tgn;
else if (nx(p) != -inf) nx(p) += tgo;
if (nn(p) == mx(p)) nn(p) += tgx;
else if (nn(p) != inf) nn(p) += tgo;
mx(p) += tgx;
mn(p) += tgn;
tgx(p) += tgx;
tgn(p) += tgn;
tgo(p) += tgo;
}
void psdn(int p) {
int mx = max(mx(lc), mx(rc)), mn = min(mn(lc), mn(rc));
tgdn(lc, mx == mx(lc) ? tgx(p) : tgo(p), mn == mn(lc) ? tgn(p) : tgo(p), tgo(p));
tgdn(rc, mx == mx(rc) ? tgx(p) : tgo(p), mn == mn(rc) ? tgn(p) : tgo(p), tgo(p));
tgx(p) = tgn(p) = tgo(p) = 0;
}
void updt(int p, int l, int r, int x) {
if (l > r || l > r(p) || l(p) > r) return;
if (l <= l(p) && r(p) <= r) return tgdn(p, x, x, x);
psdn(p);
updt(lc, l, r, x), updt(rc, l, r, x);
psup(p);
}
void upmx(int p, int l, int r, int x) {
if (mn(p) >= x || l > r || l > r(p) || l(p) > r) return;
if (l <= l(p) && r(p) <= r && nn(p) > x) return tgdn(p, 0, x - mn(p), 0);
psdn(p);
upmx(lc, l, r, x), upmx(rc, l, r, x);
psup(p);
}
void upmn(int p, int l, int r, int x) {
if (mx(p) <= x || l > r || l > r(p) || l(p) > r) return;
if (l <= l(p) && r(p) <= r && nx(p) < x) return tgdn(p, x - mx(p), 0, 0);
psdn(p);
upmn(lc, l, r, x), upmn(rc, l, r, x);
psup(p);
}
int qusm(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return 0;
if (l <= l(p) && r(p) <= r) return sm(p);
psdn(p);
return qusm(lc, l, r) + qusm(rc, l, r);
}
int qumx(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return -inf;
if (l <= l(p) && r(p) <= r) return mx(p);
psdn(p);
return max(qumx(lc, l, r), qumx(rc, l, r));
}
int qumn(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return inf;
if (l <= l(p) && r(p) <= r) return mn(p);
psdn(p);
return min(qumn(lc, l, r), qumn(rc, l, r));
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
bud(1, 1, n);
cin >> m;
while (m--) {
int o, l, r;
cin >> o >> l >> r;
if (o == 1) {
int x;
cin >> x;
updt(1, l, r, x);
}
else if (o == 2) {
int x;
cin >> x;
upmx(1, l, r, x);
}
else if (o == 3) {
int x;
cin >> x;
upmn(1, l, r, x);
}
else if (o == 4) cout << qusm(1, l, r) << '\n';
else if (o == 5) cout << qumx(1, l, r) << '\n';
else cout << qumn(1, l, r) << '\n';
}
return 0;
}
区间历史最值
不带区间修改最值
题意:区间求
区间历史最值问题采用普通标记维护的方式是困难的。注意到我们实际上需要维护的是两个值:当前区间
我们将上面两个值记作
考虑区间覆盖如何解决,一般处理这种问题加一个辅助变量就可以解决。于是:
区间加类似地修改为:
然而直接这样维护会有一个
且变换中总只有这四个值发生变化,因此只维护这四个值即可。需要留意的是
然后直接维护就可以了。
带区间修改最值
就是还要求区间取
和上面的维护方式是相近的,数域划分一下维护最小值的标记就可以了。这里给出代码:
#include <bits/stdc++.h>
#define N 500005
#define int long long
using namespace std;
const int inf = 1e15;
int n, m;
int a[N];
struct mar {
int a, b;
};
mar operator + (const mar &a, const mar &b) {
return {a.a + b.a, max(a.a + b.b, a.b)};
}
struct Node {
int l, r;
int sm, ct;
int mx, hmx;
int nx, hnx;
mar tm, tn;
} e[N << 2];
#define l(i) e[i].l
#define r(i) e[i].r
#define sm(i) e[i].sm
#define ct(i) e[i].ct
#define mx(i) e[i].mx
#define hmx(i) e[i].hmx
#define nx(i) e[i].nx
#define hnx(i) e[i].hnx
#define tm(i) e[i].tm
#define tn(i) e[i].tn
#define lc (p << 1)
#define rc (lc | 1)
void push_up(int p) {
sm(p) = sm(lc) + sm(rc);
mx(p) = max(mx(lc), mx(rc));
hmx(p) = max(hmx(lc), hmx(rc));
if (mx(lc) == mx(rc)) {
ct(p) = ct(lc) + ct(rc);
nx(p) = max(nx(lc), nx(rc));
hnx(p) = max(hnx(lc), hnx(rc));
}
else if (mx(lc) > mx(rc)) {
ct(p) = ct(lc);
nx(p) = max(nx(lc), mx(rc));
hnx(p) = max({hnx(lc), hnx(rc), mx(rc)});
}
else {
ct(p) = ct(rc);
nx(p) = max(mx(lc), nx(rc));
hnx(p) = max({hnx(lc), hnx(rc), mx(lc)});
}
}
void build(int p, int l, int r) {
l(p) = l, r(p) = r;
tm(p) = tn(p) = {0, -inf};
if (l == r) {
mx(p) = hmx(p) = sm(p) = a[l];
ct(p) = 1;
nx(p) = hnx(p) = -inf;
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
push_up(p);
}
void tgdn(int p, mar tm, mar tn) {
sm(p) += ct(p) * tm.a + (r(p) - l(p) + 1 - ct(p)) * tn.a;
hmx(p) = max(hmx(p), mx(p) + tm.b);
mx(p) += tm.a;
if (nx(p) != -inf) hnx(p) = max(hnx(p), nx(p) + tn.b), nx(p) += tn.a;
tm(p) = tm(p) + tm;
tn(p) = tn(p) + tn;
}
void psdn(int p) {
int mx = max(mx(lc), mx(rc));
tgdn(lc, mx(lc) == mx ? tm(p) : tn(p), tn(p));
tgdn(rc, mx(rc) == mx ? tm(p) : tn(p), tn(p));
tm(p) = tn(p) = {0, -inf};
}
void updt(int p, int l, int r, int x) {
if (l > r || l > r(p) || l(p) > r) return;
if (l <= l(p) && r(p) <= r) return tgdn(p, {x, x}, {x, x});
psdn(p);
updt(lc, l, r, x), updt(rc, l, r, x);
push_up(p);
}
void upmn(int p, int l, int r, int x) {
if (x >= mx(p) || l > r || l > r(p) || l(p) > r) return;
if (l <= l(p) && r(p) <= r && nx(p) < x) return tgdn(p, {x - mx(p), x - mx(p)}, {0, -inf});
psdn(p);
upmn(lc, l, r, x), upmn(rc, l, r, x);
push_up(p);
}
int qsm(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return 0;
if (l <= l(p) && r(p) <= r) return sm(p);
psdn(p);
return qsm(lc, l, r) + qsm(rc, l, r);
}
int qmx(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return -inf;
if (l <= l(p) && r(p) <= r) return mx(p);
psdn(p);
return max(qmx(lc, l, r), qmx(rc, l, r));
}
int qnx(int p, int l, int r) {
if (l > r || l > r(p) || l(p) > r) return -inf;
if (l <= l(p) && r(p) <= r) return hmx(p);
psdn(p);
return max(qnx(lc, l, r), qnx(rc, l, r));
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--) {
int o, l, r;
cin >> o >> l >> r;
if (o == 1) {
int x;
cin >> x;
updt(1, l, r, x);
}
else if (o == 2) {
int x;
cin >> x;
upmn(1, l, r, x);
}
else if (o == 3) cout << qsm(1, l, r) << '\n';
else if (o == 4) cout << qmx(1, l, r) << '\n';
else cout << qnx(1, l, r) << '\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!