线段树复习笔记
线段树(Segment Tree)复习笔记
By Chesium 2021/10/21
- 线段树能够维护满足以下条件的区间问题:
\[f(l,r)=g[f(l,m),f(m+1,r)]
\]
即区间 \([l,r]\) 的答案可以通过合并 \([l,m]\) 和 \([m+1,r]\) 的答案得到。
如这些可以:区间和、区间最大值。
如这些不行:区间众数、区间最长不下降子序列
-
数组 \(d\) 长度开至 \(4n\) 即可。
-
建树:递归思想
- 时间复杂度:\(\mathrm{O}(n)\)
- 参数:对应数组闭区间 \([s,t]\),以及当前处理的 \(d\) 数组索引 \(p\)( \(p\) 一开始是 \(1\),不能为 \(0\))
- 若 \(s=t\),就不用再细分了,直接令 \(d[p]=a[s]\),返回。
- 否则,则二分处理:令 \(m=s+((t-s)>\!\!>1)\) ,即为数组中间索引,将 \([s,t]\) 分成 \([s,m]\) 和 \([m+1,t]\) 两部分。分别建树,两者对应的索引 \(p\) 分别为 \(2p\) 和 \(2p+1\)
- 建完树后,我们便把上述两区间合并,形成当前正在建立的区间的值。
-
单点修改:DFS
- 时间复杂度:\(\mathrm{O}(\log n)\)
- 参数:需更新元素索引 \(k\),修改值 \(c\),当前节点对应区间 \([s,t]\),当前节点编号(\(d\) 数组索引)\(p\) 。
- 若 \(s=t\),则当前区间中只有一个元素,肯定是我们要改的,直接修改 \(d[p]\),返回。
- 计算出中位索引 \(m\)。
- 若 \(x\leq m\),则说明修改值在 \([s,m]\) 中,否则在 \([m+1,t]\) 中,递归更新。
- 回溯时更新自己的值 \(d[p]\)。
-
无更新查询:递归思想
- 时间复杂度:\(\mathrm{O}(\log n)\)
- 参数:查询区间 \([l,r]\),当前节点对应区间 \([s,t]\),当前节点编号(\(d\) 数组索引)\(p\) 。
- 可以理解为求查询区间与当前节点区间交集的对应值
- 若 \(l\leq s\ \wedge\ t\leq r\),即 \([s,t]\subseteq[l,r]\),当前区间必是最终查询值的组成部分,称为查询区间的一个极大区间,直接返回自己的值 \(d[p]\)。
- 否则,二分为上面讲过的两区间 \([s,m]\) 和 \([m+1,t]\),不改变 \([l,r]\),进行查询。
- 若 \(l\leq m\),则 \([s,m]\cap[l,r]\neq\varnothing\) ,需要考虑,查询 \([s,m]\)。
- 若 \(r>m\),则 \([m+1,t]\cap[l,r]\neq\varnothing\) ,需要考虑,查询 \([m+1,t]\)。
- 回溯时合并区间即可。
-
区间修改:懒惰标记
- 时间复杂度:\(\mathrm{O}(\log n)\)
- 目的:通过延迟对节点信息的更改,从而减少可能不必要的操作次数。
- 参数:修改区间 \([l,r]\),修改值 \(c\),当前节点对应区间 \([s,t]\),当前节点编号(\(d\) 数组索引)\(p\) 。
- 若 \(l\leq s\ \wedge\ t\leq r\),即 \([s,t]\subseteq[l,r]\),当前区间是修改区间的子集,肯定要改:修改 \(d[p]\),添加标记 \(b[p]\)。(更新标记,某些操作为往上叠加(如增加),有些是覆盖(如赋值)),返回。
- 若有标记存在则进行标记下传(防止父区间的懒惰标记下传时覆盖子区间的懒惰标记)
- 类似上述的无更新查询,分别考虑二分后的两区间是否需要进行操作。
- 回溯时记得合并区间!
-
标记下传
- 时间复杂度:\(\mathrm{O}(1)\)
- 参数:当前节点对应区间 \([s,t]\),当前节点编号(\(d\) 数组索引)\(p\) 。
- 若无标记,则直接返回。
- 若 \(s=t\),则标记无作用,也没有区间用于下传,直接返回。
- 求出 \(m\) ,根据区间范围修改 \(d[2p]\) 与 \(d[2p+1]\) 的值
- 更新子区间的懒惰标记 \(b[2p]\) 和 \(b[2p+1]\)
- 清除自身的懒惰标记。
-
带修改区间查询
- 时间复杂度:\(\mathrm{O}(\log n)\)
基本同无修改版本,只不过在二分求解前进行一次标记下传。
- 时间复杂度:\(\mathrm{O}(\log n)\)
-
参考代码
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
//---//
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
typedef unsigned int u;
typedef long long ll;
typedef unsigned long long llu;
#define rep(i, a, b) for (ll i = a; i < b; i++)
#define REP(i, a, b) for (ll i = a; i <= b; i++)
#define per(i, b, a) for (ll i = b; i >= a; i--)
// P3372 【模板】线段树 1
#define get_mid ll m = s + ((t - s) >> 1)
const ll N = 1e5 + 10;
ll a[N], d[4 * N], b[4 * N];
bool lz[4 * N];
void build(ll s, ll t, ll p) {
if (s == t) {
d[p] = a[s];
return;
}
get_mid;
build(s, m, 2 * p);
build(m + 1, t, 2 * p + 1);
d[p] = d[2 * p] + d[2 * p + 1];
}
ll push_down(ll s, ll t, ll p) {
get_mid;
if (s == t || (!lz[p])) return m;
b[2 * p] += b[p];
b[2 * p + 1] += b[p];
d[2 * p] += b[p] * (m - s + 1);
d[2 * p + 1] += b[p] * (t - m);
lz[2 * p] = true;
lz[2 * p + 1] = true;
b[p] = 0;
lz[p] = false;
return m;
}
void update(ll l, ll r, ll c, ll s, ll t, ll p) {
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c;
b[p] += c;
lz[p] = true;
return;
}
ll m = push_down(s, t, p);
if (l <= m) update(l, r, c, s, m, 2 * p);
if (r > m) update(l, r, c, m + 1, t, 2 * p + 1);
d[p] = d[2 * p] + d[2 * p + 1];
}
ll query(ll l, ll r, ll s, ll t, ll p) {
if (l <= s && t <= r) {
return d[p];
}
ll sum = 0, m = push_down(s, t, p);
if (l <= m) sum += query(l, r, s, m, 2 * p);
if (r > m) sum += query(l, r, m + 1, t, 2 * p + 1);
return sum;
}
signed main() {
ll n, m, t, l, r, k;
scanf("%lld%lld", &n, &m);
REP(i, 1, n) scanf("%lld", &a[i]);
build(1, n, 1);
while (m--) {
scanf("%lld%lld%lld", &t, &l, &r);
if (t == 1) {
scanf("%lld", &k);
update(l, r, k, 1, n, 1);
} else
printf("%lld\n", query(l, r, 1, n, 1));
}
}
// https://www.luogu.com.cn/record/60473191
- P3373 【模板】线段树 2 - 洛谷这道题中有两种区间修改操作:加法和乘法,我们需要两个懒惰标记来分别记录它们。
标记下传中应该先乘后加:
如将序列先 \(+k\),再 \(\times m\) ,再 \(+t\),则新的元素和应为:
\[\mathtt{sum'}=\sum_{i=l}^rm(a_i+k)+t=(r-l+1)\underbrace{(mk+t)}_\mathtt{add}+\underbrace{m}_\mathtt{mul}\sum_{i=l}^ra_i=\mathtt{len*add+mul*sum}
\]
由于 \(\mathtt{add}=mk+t\),所以将序列 \(\times m\) 时应将 \(\mathtt{add}\) 值也 \(\times m\) 。
- 参考代码:
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
//---//
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
typedef unsigned int u;
typedef long long ll;
typedef unsigned long long llu;
#define rep(i, a, b) for (ll i = a; i < b; i++)
#define REP(i, a, b) for (ll i = a; i <= b; i++)
#define per(i, b, a) for (ll i = b; i >= a; i--)
// P3372 【模板】线段树 2
#define get_mid ll m = s + ((t - s) >> 1)
const ll N = 1e5 + 10;
ll mod, a[N], d[4 * N], add[4 * N], mul[4 * N];
bool lz[4 * N];
void build(ll s, ll t, ll p) {
mul[p] = 1;
if (s == t) {
d[p] = a[s];
return;
}
get_mid;
build(s, m, 2 * p);
build(m + 1, t, 2 * p + 1);
d[p] = (d[2 * p] + d[2 * p + 1]) % mod;
}
ll push_down(ll s, ll t, ll p) {
get_mid;
if (s == t || (!lz[p])) return m;
d[2 * p] = (add[p] * (m - s + 1) % mod + d[2 * p] * mul[p] % mod) % mod;
d[2 * p + 1] = (add[p] * (t - m) % mod + d[2 * p + 1] * mul[p] % mod) % mod;
add[2 * p] = (add[2 * p] * mul[p] % mod + add[p]) % mod;
add[2 * p + 1] = (add[2 * p + 1] * mul[p] % mod + add[p]) % mod;
mul[2 * p] = mul[2 * p] * mul[p] % mod;
mul[2 * p + 1] = mul[2 * p + 1] * mul[p] % mod;
lz[2 * p] = true;
lz[2 * p + 1] = true;
add[p] = 0;
mul[p] = 1;
lz[p] = false;
return m;
}
void update_add(ll l, ll r, ll c, ll s, ll t, ll p) {
if (l <= s && t <= r) {
d[p] = ((t - s + 1) * c % mod + d[p]) % mod;
add[p] = (c + add[p]) % mod;
lz[p] = true;
return;
}
ll m = push_down(s, t, p);
if (l <= m) update_add(l, r, c, s, m, 2 * p);
if (r > m) update_add(l, r, c, m + 1, t, 2 * p + 1);
d[p] = (d[2 * p] + d[2 * p + 1]) % mod;
}
void update_mul(ll l, ll r, ll c, ll s, ll t, ll p) {
if (l <= s && t <= r) {
d[p] = d[p] * c % mod;
add[p] = add[p] * c % mod;
mul[p] = (mul[p] * c) % mod;
lz[p] = true;
return;
}
ll m = push_down(s, t, p);
if (l <= m) update_mul(l, r, c, s, m, 2 * p);
if (r > m) update_mul(l, r, c, m + 1, t, 2 * p + 1);
d[p] = (d[2 * p] + d[2 * p + 1]) % mod;
}
ll query(ll l, ll r, ll s, ll t, ll p) {
if (l <= s && t <= r) return d[p];
ll sum = 0, m = push_down(s, t, p);
if (l <= m) sum = (sum + query(l, r, s, m, 2 * p)) % mod;
if (r > m) sum = (sum + query(l, r, m + 1, t, 2 * p + 1)) % mod;
return sum;
}
signed main() {
ll n, m, t, l, r, k;
scanf("%lld%lld%lld", &n, &m, &mod);
REP(i, 1, n) scanf("%lld", &a[i]);
build(1, n, 1);
while (m--) {
scanf("%lld%lld%lld", &t, &l, &r);
switch (t) {
case 1:
scanf("%lld", &k);
update_mul(l, r, k, 1, n, 1);
break;
case 2:
scanf("%lld", &k);
update_add(l, r, k, 1, n, 1);
break;
case 3:
printf("%lld\n", query(l, r, 1, n, 1));
break;
}
}
}
// https://www.luogu.com.cn/record/60476253