带懒标记的线段树模板
模板_ 线段树
带懒标记的线段树
性质
线段树是运用分块思想的树形结构。其主要的作用是维护区间信息的数据结构。
线段树可以在 \(O(\text{log}(n))\)的时间复杂度内完成单点修改、区间修改、区间查询等操作。
值得注意的是线段树能够维护的信息需要满足可加性。即例如\(max(a,b,c)=max(max(a,b),c)=max(max(a,c),b)=max(max(b,c),a)\)满足可加性,而\((a\mod b \mod c )\neq( a\mod c \mod b)\)不满足可加性。
可加性是指对于某种变换来说,特定的“加法”和该变换的顺序可颠倒而不影响结果,这样一种性质。——来自wiki百科
操作
| 符号 | 含义 |
|---|---|
| a | 原数组 |
| d | 线段树节点维护值 |
| lazytag | 线段树节点的懒标记值 |
| p | 当前的节点 |
| s | 查询区间的开始 |
| e | 查询区间的结尾 |
| l | 节点区间的开始 |
| r | 节点区间的结尾 |
一般习惯:
- 建树从下标为1开始
- mid = l + r >> 1
建立线段树
以\(a=\{1,2,3\}\)为例子,树的结构如下:

首先是提供的参数void build(int s, int e, int p)。
然后是伪代码:
void build(int s, int e, int p){
if(s == e){
//当前d[p]是叶子节点
d[p]=a[p];
return;
}
int m = s + e >> 1;
build(左子树[s,m]);
build(左子树[m+1,e]);
pushup(p);
}
即为:对于一个节点,我们从根节点开始,递归的构建整个树到叶子节点,并且从叶子节点\(\text{pushup}\)到根节点。
对于一个节点:
- 若当前的节点是叶子节点,即
s==e,那么\(d[p]\)(对应区间\([s,s]\))的就是\(a[s]\) - 若当前的节点是非叶子节点,即
s<e,那么将区间分割为两个部分即\([s,m]\)(对应下标为\(p<<1\))和\([m+1,e]\)(对应的下标为\(p<<1|1\)),其中\(m=s+e>>1\),递归到左右儿子进行建树。
void build(ll s, ll e, ll p) {
//[s,e]建立线段树 根为p
if (s == e) {
d[p] = a[s];
return;
}
ll m = (s + e) >> 1;
build(s, m, p << 1), build(m + 1, e, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
区间查询
对于一个节点,其存储的是\([l,r]\)区间内的维护的值,而对于一个区间查询\([s,e]\)
若有查询的区间\([s,e]\)完全覆盖当前的节点区间\([l,r]\),即s<=l && r<=e查询的值即为\(d[p]\)

否则,将区间一分为二:
- \([l,m]\)左儿子对应下标为\(p<<1\)
- \([m+1,r]\)右儿子对应的下标为\(p<<1|1\)
此时递归到左右儿子子树进行查询

如果每一次更新区间值,都会使得整个线段树向下更新到根节点。在区间更新的时候应该使用懒标记的线段树,延迟整个节点的信息更新。
带有懒标记的线段树,实际上是父节点暂时记录了下推到子节点的信息。在查询时,才将延迟更新的节点信息加载到子节点。
对于参数\(ll getsum(ll l, ll r, ll s, ll e, ll p)\)
ll getsum(ll l, ll r, ll s, ll e, ll p) {
if(当前区间是叶节点) return d[p]
ll m = l + r >> 1;
if(lazy[p]){//当前的节点是带有延迟更新的信息的
//pushdown
d[p的左儿子] += lazy[p] * (p左儿子区间长度);
d[p的右儿子] += lazy[p] * (p右儿子区间长度);
lazy[p的左儿子] += lazy[p];
lazy[p的右儿子] += lazy[p];
}
lazy[p] = 0;//还原懒惰标记
ll sum = 0;//查询值
if(左儿子存在) sum += getsum(l,r,左儿子区间,左儿子下标);
if(右儿子存在) sum += getsum(l,r,右儿子区间,右儿子下标);
return sum;
}
这里为了将清楚流程,借用oi-wiki的图片:





此时如果当前的区间进行查询[1,1](即为B节点):
那么A的信息将会传递给B,这个过程即为\(pushdown\)过程。


ll getsum(ll l, ll r, ll s, ll e, ll p) {
/*
[l,r]当前查询的区间
[s,e]当前节点的区间
p 为当前的节点编号
*/
if (l <= s && e <= r) return d[p];
ll m = (s + e) / 2, sum = 0;
//更新子节点和传递懒标记
if (lazy[p]){
d[p << 1] += lazy[p] * (m - s + 1), d[p << 1 | 1] += lazy[p] * (e - m);
lazy[p << 1] += lazy[p], lazy[p << 1 | 1] += lazy[p];
}
lazy[p] = 0; //还原懒标记
if (l <= m) sum += getsum(l, r, s, m, p << 1);
if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
return sum;
}
区间更新
综上所述,区间修改(更新)过程是产生新的懒标记,而区间查询是将懒标记向下传递\(pushdown\)。
函数参数void update(ll l, ll r, ll c, ll s, ll e, ll p)
伪码描述:
void update(ll l, ll r, ll c, ll s, ll e, ll p) {
if(当前的查询区间完全包裹节点) {
d[p] += 当前节点区间长度 * 修改的值c;//即把区间内的所有点都加上c,等效于这个区间加上了len*c
lazy[p] += c;//区间更新的值向下传递
return ;
}
ll m = l + r >> 1;
if(lazy[p]){//当前的节点是带有延迟更新的信息的
//pushdown(p);
d[p的左儿子] += lazy[p] * (p左儿子区间长度);
d[p的右儿子] += lazy[p] * (p右儿子区间长度);
lazy[p的左儿子] += lazy[p];
lazy[p的右儿子] += lazy[p];
}
if(左儿子存在) update(l,r,左儿子区间,左儿子下标);
if(右儿子存在) update(l,r,右儿子区间,右儿子下标);
pushup(p);
}
即为
void update(ll l, ll r, ll c, ll s, ll e, ll p) {
/*
带有懒标记的更新操作
[l,r]当前查询的区间
[s,e]当前节点的区间
c维护的信息
p节点位置
*/
if (l <= s && e <= r) {
d[p] += (e - s + 1) * c;
lazy[p] += c;
return;
}
ll m = (s + e) >> 1;
//叶子节点不需要下放懒标记
if (lazy[p]) { //当前的节点懒标记可以向下传递
//左儿子和右儿子更新节点信息
d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
lazy[p] = 0; //消除懒标记
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
pushup
该部分操作是递归到儿子时,回溯地影响自己的父亲节点,是自底向上的修改。
涉及的操作是build和update。
本质是更新了子节点后,重新计算父节点的信息。
操作为
d[p] = d[p << 1] + d[(p << 1) | 1];
pushdown
该部分操作是把父亲欠下儿子的信息向下返还,用懒惰标记更新当前的维护值。
涉及的操作是getsum和update。
if (lazy[p]) { //当前的节点懒标记可以向下传递
//左儿子和右儿子更新节点信息
d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
lazy[p] = 0; //消除懒标记
}
复杂度
在\(O(\text{log}(n))\)的时间复杂度内完成单点修改、区间修改、区间查询等操作
空间复杂度较高,上限是\(O(9*n)\)(在默认开4倍的空间的情况下,注意!在1e5容易爆)
空间的复杂度上限为\(O(2^{\lceil{\text{log}(n)+1} \rceil })\),但是实际竞赛中一般取\(4*\text{maxn}\)。
模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
static int faster_iostream = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(NULL);
return 0;
}();
const int maxn = 1e5 + 10;
const int maxx = (int)ceil(log(1e5+10)/log(2))*1.2;//270000
/*a是原数组,d是线段树维护的信息,b是懒标记*/
/*
1. 懒标记b>0说明当前的节点欠儿子某些信息
pushdown在更新懒标记节点的时候,还要向下传递
更新其儿子的懒标记信息
2. 同时也更新儿子维护的信息d(信息的一半)
*/
// 262144 == 1 << ((int)ceil(log(1e5 + 10) / log(2)) + 1);
ll a[maxn], d[262144], lazy[262144];
void build(ll s, ll e, ll p) {
//[s,e]建立线段树 根为p
if (s == e) {
d[p] = a[s];
return;
}
ll m = (s + e) >> 1;
build(s, m, p << 1), build(m + 1, e, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
ll getsum(ll l, ll r, ll s, ll e, ll p) {
/*
[l,r]当前查询的区间
[s,e]当前节点的区间
p 为当前的节点编号
*/
if (l <= s && e <= r) return d[p];
ll m = (s + e) / 2, sum = 0;
//更新子节点和传递懒标记
if (lazy[p])
d[p << 1] += lazy[p] * (m - s + 1), d[p << 1 | 1] += lazy[p] * (e - m),
lazy[p << 1] += lazy[p], lazy[p << 1 | 1] += lazy[p];
lazy[p] = 0; //还原懒标记
if (l <= m) sum += getsum(l, r, s, m, p << 1);
if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
return sum;
}
void update(ll l, ll r, ll c, ll s, ll e, ll p) {
/*
带有懒标记的更新操作
[l,r]当前查询的区间
[s,e]当前节点的区间
c维护的信息
p节点位置
*/
if (l <= s && e <= r) {
d[p] += (e - s + 1) * c;
lazy[p] += c;
return;
}
ll m = (s + e) >> 1;
//叶子节点不需要下放懒标记
if (lazy[p]) { //当前的节点懒标记可以向下传递
//左儿子和右儿子更新节点信息
d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
lazy[p] = 0; //消除懒标记
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
ll n, q;
int main() {
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, n, 1);
ll a, b, c, d;
while (q--) {
cin >> a >> b >> c;
if (a == 2) {
cout << getsum(b, c, 1, n, 1) << endl;
} else {
cin >> d;
update(b, c, d, 1, n, 1);
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll a[maxn], lazy[300000], d[300000];
void pushup(ll p) { d[p] = d[p << 1] + d[p << 1 | 1]; }
void pushdown(ll p, ll s, ll m, ll e) {
d[p << 1] += lazy[p] * (m - s + 1);
d[p << 1 | 1] += lazy[p] * (e - m);
lazy[p << 1] += lazy[p];
lazy[p << 1 | 1] += lazy[p];
lazy[p] = 0;
}
void build(ll s, ll e, ll p) {
if (s == e) {
d[p] = a[s];
return;
}
ll m = s + e >> 1;
build(s, m, p << 1);
build(m + 1, e, (p << 1) | 1);
pushup(p);
}
ll getsum(ll l, ll r, ll s, ll e, ll p) {
if (l <= s && r >= e) return d[p];
ll m = (s + e) >> 1;
if (lazy[p]) {
pushdown(p, s, m, e);
}
ll sum = 0;
if (l <= m) sum += getsum(l, r, s, m, p << 1);
if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
return sum;
}
void update(ll l, ll r, ll s, ll e, ll c, ll p) {
if (l <= s && r >= e) {
d[p] += (e - s + 1) * c;
lazy[p] += c;
return;
}
ll m = (s + e) >> 1;
if (lazy[p]) {
pushdown(p, s, m, e);
}
if (l <= m) update(l, r, s, m, c, p << 1);
if (m < r) update(l, r, m + 1, e, c, p << 1 | 1);
pushup(p);
}
ll m, n;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, n, 1);
ll a, b, c, d;
while (m--) {
cin >> a >> b >> c;
if (a == 2) {
//查询bc
cout << getsum(b, c, 1, n, 1) << endl;
} else {
cin >> d;
update(b, c, 1, n, d, 1);
}
}
return 0;
}

浙公网安备 33010602011771号