线段树
线段树的结构
对于大量数据信息的查询和修改,普通数组O(n)的时间复杂度已经远远不能满足需求,为此计算机科学家们利用二分法的思想,创造出了一种巧妙的数据结构——线段树,它的查询与修改的时间复杂度为O(logn)。
线段树从字面上我们可以拆成两部分,“线段”指它是建立在区间上的,“树”指它是一个二叉树。
对于任何一个数据集,一般情况下我们会将其存在数组a[n]中。但使用线段树这种数据结构后,我们所建立的数组中每个元素存储的并不是单一的数据,而是一段区间的区间和。即
c
[
i
]
=
∑
j
=
l
r
a
[
j
]
c[i]=\sum_{j=l}^ra[j]
c[i]=∑j=lra[j]
线段树的本质是将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个结点。
它的结构如下图。
对于任意的结点c[i],它的左儿子是c[i * 2] ,右儿子是c[i * 2 + 1]
若c[i]管理的区间是[a, b],则它的左儿子c[i * 2]管理的区间是[a, (a + b) / 2],它的右儿子管理的区间是[(a + b) / 2 + 1, b]
线段树的根结点c[1]管理的是整个区间。
线段树的构造与信息维护(包括最大值,最小值,区间和)
线段树的空间复杂度
设数组a的大小为n,当n不是2的幂次时,线段树c的深度为
[
l
o
g
2
n
]
+
2
[log_2^n]+2
[log2n]+2([]为向下取整);当n是二的幂次时,线段树c的深度为
[
l
o
g
2
n
+
1
]
[log_2^n+1]
[log2n+1]。因此,当n不是2的幂次时,线段树c所需要的空间为
2
[
l
o
g
2
n
]
+
2
−
1
2^{[log_2^n]+2}-1
2[log2n]+2−1,当n是2的幂次时;线段树c所需要的空间为
2
[
l
o
g
2
n
]
+
1
−
1
2^{[log_2^n]+1}-1
2[log2n]+1−1
我们考虑最坏的情况,即n较大,且n不是2的幂次但十分靠近2的幂次。因为在这种情况下,线段树所需要的空间是
2
[
l
o
g
2
n
]
+
2
−
1
2^{[log_2^n]+2}-1
2[log2n]+2−1,并且
[
l
o
g
2
n
]
≈
l
o
g
2
n
[log_2^n]\approx log_2^n
[log2n]≈log2n,所以所需的空间约为
4
n
−
1
4n-1
4n−1
因此,线段树的空间复杂度为O(4n)
线段树的构造
我们在构造线段树时需要存储每个结点所管理的区间端点信息,以便我们快速确定当前的结点管理的是哪个区间。
其实在线段树的每个结点中我们可以存储很多我们需要的信息,比如区间和,区间最大值,区间最小值等,只要这些信息满足结合律(即任意区间的信息等于其所有子区间信息之并),我们都可以通过线段树来管理并维护这些信息。
什么是满足结合律呢?
比如:一个区间的最大(小)值等于其所有子区间最大(小)值中的最大(小)值。一个区间的区间和等于其所有子区间区间和相加。
结点:
class Node
{
public:
int l, r;
long long sum, max, min;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
}
};
在构造线段树时,我们采用递归建树的方式,将上一层的一个大区间分成下一层的左右两个小区间,不断重复这个操作直到最后一层的区间中只剩下一个元素,无法再分。
递归建树:
void build(int l, int r, int i)//i指线段树c的第i个结点c[i],l指c[i]的左端点,r指c[i]的右端点。
{
c[i].l = l;
c[i].r = r;
if (l == r) return;//当l = r时说明区间只剩一个元素
build(l, (l + r) / 2, i * 2);
build((l + r) / 2 + 1, r, i * 2 + 1);
}
int main()
{
Node c[size * 4];
build(1, size, 1);//先从整个区间[1, size]开始划分
}
封装成类:
//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
public:
int l, r;
long long sum, max, min;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
}
};
class SegTree : vector<Node>
{
public:
int size;
explicit SegTree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size * 4, Node());//空间复杂度为O(4n)
build(1, size, 1);
}
private:
void build(int l, int r, int loc)
{
(*this)[loc].l = l;
(*this)[loc].r = r;
if (l == r) return;
build(l, (l + r) / 2, loc * 2);
build((l + r) / 2 + 1, r, loc * 2 + 1);
}
};
单点修改与pushup()函数
当我们想修改a数组中的一个元素a[i]时就要修改线段树c中的若干个结点,这些结点均管理着包含a[i]的一段区间。
如下图,当我们想要修改a[4]时,就要修改c[1]、c[2]、c[5]、c[10]、c[20]
寻找这些结点可以用迭代的方法:从根节点开始,每次都判断i在左区间还是在右区间,然后进入线段树的下一层,重复上述操作,直到最后一层。以a[4]为例:
当我们迭代到最后一层时,开始进行回溯操作,从最底层开始不断修改迭代时走过的区间。
如图:
从底层向上层修改信息的回溯操作我们称之为pushup()函数:
void pushup(int i)
{
c[i].max = max(c[i * 2].max, c[i * 2 + 1].max);//区间最大值等于其左右子区间最大值中的最大值
c[i].min = min(c[i * 2].min, c[i * 2 + 1].min);//区间最小值等于其左右子区间最小值中的最小值
c[i].sum = c[i * 2].sum + c[i * 2 + 1].sum;//区间和等于其左右子区间区间和相加
}
整个修改过程可以用递归实现:
void update(int loc, long long val, int i)
{
if (c[i].l != c[i].r)//如果c[i]管理的区间内不只有一个元素,说明还没到达目标位置,要继续递推下去
{
if ((c[i].l + c[i].r) / 2 >= loc) update(loc, val, i * 2);//判断目标位置是否在左区间
else update(loc, val, i * 2 + 1);//判断目标位置是否在右区间
pushup(i);//回溯,更新区间信息
}
else//如果c[i]是叶结点(即区间内只有一个元素),说明到达目标位置
{
c[i].max += val;
c[i].min += val;
c[i].sum += val;
}
}
封装成类:
//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
public:
int l, r;
long long sum, max, min;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
}
};
class SegTree : vector<Node>
{
public:
int size;
explicit SegTree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size * 4, Node());
build(1, size, 1);
}
void update(int loc, long long val)//单点修改
{
if (loc < 1 || loc > size) return;
this->loc = loc;
this->val = val;
_update(1);
}
private:
int loc;
long long val;
void build(int l, int r, int loc)
{
(*this)[loc].l = l;
(*this)[loc].r = r;
if (l == r) return;
build(l, (l + r) / 2, loc * 2);
build((l + r) / 2 + 1, r, loc * 2 + 1);
}
void pushup(int i)
{
(*this)[i].max = max((*this)[i * 2].max, (*this)[i * 2 + 1].max);
(*this)[i].min = min((*this)[i * 2].min, (*this)[i * 2 + 1].min);
(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2 + 1].sum;
}
void _update(int i)
{
if ((*this)[i].l != (*this)[i].r)
{
if (((*this)[i].l + (*this)[i].r) / 2 >= loc) _update(i * 2);
else _update(i * 2 + 1);
pushup(i);
}
else
{
(*this)[i].max += val;
(*this)[i].min += val;
(*this)[i].sum += val;
}
}
};
区间修改与lazy标记
在进行区间修改时,我们往往要修改线段树中的若干个结点。
如图,当我们想要将[1, 11]区间内全部元素加k时,就要修改线段树中的所有结点,这样时间复杂的就退化到了O(n),为了保证线段树区间修改的时间复杂度依然为O(logn),我们引入了lazy标记。当某一个区间需要整体都加k时,我们将这个区间的lazy标记加k,而不继续向下进行,省去了修改下层子区间的时间。
如图,当我们想要修改[1, 9]时只需修改黄色结点,而不用lazy标记需要修改的是蓝色结点。
因此结点类需要添加一个lazy标记:
class Node
{
public:
int l, r;
long long sum, max, min, lazy;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
lazy = 0;
}
};
由于引入了lazy标记,pushup()函数也要做相应的修改。很显然,当一个区间的lazy标记为k时,这个区间的最大值为max + k,最小值为min + k,区间和为sum + (r - l + 1) * k:
void pushup(int i)
{
c[i].max = max(c[i * 2].max + c[i * 2].lazy, c[i * 2 + 1].max + c[i * 2 + 1].lazy);
c[i].min = min(c[i * 2].min + c[i * 2].lazy, c[i * 2 + 1].min + c[i * 2 + 1].lazy);
c[i].sum = c[i * 2].sum + c[i * 2].lazy * (c[i * 2].r - c[i * 2].l + 1) + c[i * 2 + 1].sum + c[i * 2 + 1].lazy * (c[i * 2 + 1].r - c[i * 2 + 1].l + 1);
}
在递归更新的时候,遇到叶结点时不需要改变其lazy标记,直接更新信息。因为lazy标记代表的是这个区间内所有元素被统一加了一个数值,而叶结点管理的区间只有一个元素,不需要lazy标记。
下面是区间修改的代码:
void update(int l, int r, long long val, int i)
{
if (c[i].l == c[i].r)
{
c[i].max += val;
c[i].min += val;
c[i].sum += val;
}
else if (l <= c[i].l && c[i].r <= r) c[i].lazy += val;
else
{
if ((c[i].l + c[i].r) / 2 >= l) update(l, r, val, i * 2);
if ((c[i].l + c[i].r) / 2 < r) update(l, r, val, i * 2 + 1);
pushup(i);
}
}
封装成类:
//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
public:
int l, r;
long long sum, max, min, lazy;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
lazy = 0;
}
};
class SegTree : vector<Node>
{
public:
int size;
explicit SegTree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size * 4, Node());
build(1, size, 1);
}
void update(int l, int r, long long val)//区间修改
{
if (l < 1 || r < l || r > size) return;
loc_l = l;
loc_r = r;
this->val = val;
_update(1);
}
void update(int loc, long long val)//重载一个单点修改
{
if (loc < 1 || loc > size) return;
loc_l = loc_r = loc;
this->val = val;
_update(1);
}
private:
int loc_l, loc_r;
long long val;
void build(int l, int r, int loc)
{
(*this)[loc].l = l;
(*this)[loc].r = r;
if (l == r) return;
build(l, (l + r) / 2, loc * 2);
build((l + r) / 2 + 1, r, loc * 2 + 1);
}
void pushup(int i)
{
(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
}
void _update(int i)
{
if ((*this)[i].l == (*this)[i].r)
{
(*this)[i].max += val;
(*this)[i].min += val;
(*this)[i].sum += val;
}
else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
else
{
if (((*this)[i].l + (*this)[i].r) / 2 >= loc_l) _update(i * 2);
if (((*this)[i].l + (*this)[i].r) / 2 < loc_r) _update(i * 2 + 1);
pushup(i);
}
}
};
单点查询与pushdown()函数
不考虑lazy标记,单点查询的操作应该是这样的:从最大的区间开始,不断地判断目标位置在左子区间还是在右子区间,然后一层一层递推下去,直至目标位置,最后返回目标位置的信息。
代码如下:
long long query(int loc, int i)
{
if (c[i].l == c[i].r) return c[i].sum;
else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
else return query(i * 2 + 1);
}
但是在区间修改中,我们引入了lazy标记,这就导致了若lazy标记不为0,区间的信息并不是真实的。也许有人会提出:我在递归回溯的时候把每一层的lazy标记加上不就好了吗?
比如:
long long query(int loc, int i)
{
if (c[i].l == c[i].r) return c[i].sum;
else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2) + c[i].lazy;
else return query(i * 2 + 1) + c[i].lazy;
}
这样的确解决了问题,但是对于区间查询来说这样的操作过于繁琐。
于是我们便引入了pushdown()函数,这个函数的作用是将lazy标记向下传递。
void pushdown(int i)
{
c[i].sum += c[i].lazy * (c[i].r - c[i].l + 1);
c[i].max += c[i].lazy;
c[i].min += c[i].lazy;
if (c[i].l != c[i].r)
{
c[i * 2].lazy += c[i].lazy;
c[i * 2 + 1].lazy += c[i].lazy;
}
c[i].lazy = 0;
}
lazy标记设置的目的就是省去修改下层子区间的时间,而我们在修改时又不需要调用下层子区间的信息,因此就用lazy标记代替了整段区间的修改信息,不用具体到去修改每一个叶结点的信息。
但如果现在要查询的点被包含在了一段设有lazy标记的区间里,即我们现在需要调用下层子区间的信息了,我们就可以在不断的递推过程中顺道将lazy标记不断下传,更新每个区间的信息。
因此单点查询lazy标记的下传策略是:只要目标查询位置包含在当前的递推区间内,标记就下传。
如图,依然以修改了[1, 9]的线段树为例,现在要查询a[2]的信息。黄色结点是查询前信息被更新过的结点,蓝色结点是查询后信息被更新、标记被下传的结点,绿色结点是查询前后所有lazy标记不为0的结点。
引入pushdown()函数后的单点查询代码如下,只多了一行pushdown()函数:
long long query(int loc, int i)
{
if (c[i].lazy != 0) pushdown(i);
if (c[i].l == c[i].r) return c[i].sum;
else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
else return query(i * 2 + 1);
}
封装成类:
//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
public:
int l, r;
long long sum, max, min, lazy;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
lazy = 0;
}
};
class SegTree : vector<Node>
{
public:
int size;
explicit SegTree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size * 4, Node());
build(1, size, 1);
}
void update(int l, int r, long long val)//区间修改
{
if (l < 1 || r < l || r > size) return;
loc_l = l;
loc_r = r;
this->val = val;
_update(1);
}
void update(int loc, long long val)//重载一个单点修改
{
if (loc < 1 || loc > size) return;
loc_l = loc_r = loc;
this->val = val;
_update(1);
}
long long query(int loc)//单点查询
{
if (loc < 1 || loc > size) return 0;
this->loc = loc;
return _query(1);
}
private:
int loc_l, loc_r, loc;
long long val;
void build(int l, int r, int loc)
{
(*this)[loc].l = l;
(*this)[loc].r = r;
if (l == r) return;
build(l, (l + r) / 2, loc * 2);
build((l + r) / 2 + 1, r, loc * 2 + 1);
}
void pushup(int i)
{
(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
}
void _update(int i)
{
if ((*this)[i].l == (*this)[i].r)
{
(*this)[i].max += val;
(*this)[i].min += val;
(*this)[i].sum += val;
}
else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
else
{
if (((*this)[i].l + (*this)[i].r) / 2 >= loc_l) _update(i * 2);
if (((*this)[i].l + (*this)[i].r) / 2 < loc_r) _update(i * 2 + 1);
pushup(i);
}
}
void pushdown(int i)
{
(*this)[i].sum += (*this)[i].lazy * ((*this)[i].r - (*this)[i].l + 1);
(*this)[i].max += (*this)[i].lazy;
(*this)[i].min += (*this)[i].lazy;
if ((*this)[i].l != (*this)[i].r)
{
(*this)[i * 2].lazy += (*this)[i].lazy;
(*this)[i * 2 + 1].lazy += (*this)[i].lazy;
}
(*this)[i].lazy = 0;
}
long long _query(int i)
{
if ((*this)[i].lazy != 0) pushdown(i);
if ((*this)[i].l == (*this)[i].r) return (*this)[i].sum;
else if (loc <= ((*this)[i].l + (*this)[i].r) / 2) return _query(i * 2);
else return _query(i * 2 + 1);
}
};
区间查询
与区间修改类似,区间查询依然采用分块的思想,将要查询的区间分成线段树的若干个结点,再将这些结点的信息综合起来得到目标区间的信息。同时,区间查询与单点查询一样,都涉及到lazy标记的下传。
单点查询lazy标记的下传策略是:只要目标查询位置包含在当前的递推区间内,标记就下传。
区间查询lazy标记的下传策略是:只要目标查询区间与当前递推区间的交集不为空,标记就下传。
单点查询时,目标位置一定包含在递推区间内;区间查询时,目标区间与递推区间的交集一定不为空。
因此二者下传策略相同,都是在刚进入函数时就下传。
如图,依然以修改了[1, 9]的线段树为例,现在要查询a[3, 7]的信息。黄色结点是查询前信息被更新过的结点,蓝色结点是查询后信息被更新、标记被下传的结点,绿色结点是查询前后所有lazy标记不为0的结点。
我们回顾一下单点查询的代码:
long long query(int loc, int i)
{
if (c[i].lazy != 0) pushdown(i);
if (c[i].l == c[i].r) return c[i].sum;
else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
else return query(i * 2 + 1);
}
类比单点查询,区间查询的代码结构应该是这样的:
long long query(int l, int r, int i)
{
if (c[i].lazy != 0) pushdown(i);//相同的下传策略
//递归
}
那么递归部分的代码怎么写呢?在递归过程中,当前递归区间与目标查询区间之间有四种情况:
- 当前区间
⊆
\subseteq
⊆ 目标区间
这种情况下递归的返回值应该是当前区间的信息,
如果查询的是区间和:
if (l <= c[i].l && c[i].r <= r) return c[i].sum;
如果查询的是区间最大值:
if (l <= c[i].l && c[i].r <= r) return c[i].max;
如果查询的是区间最小值:
if (l <= c[i].l && c[i].r <= r) return c[i].min;
- 当前区间
∩
\cap
∩ 目标区间
⊇
{
m
i
d
,
m
i
d
+
1
}
\supseteq \{mid, mid + 1\}
⊇{mid,mid+1}
这种情况下递归的返回值应该是左右子区间的返回信息的并,
如果查询的是区间和:
else if (l <= mid && r > mid) return query(l, r, i * 2) + query(l, r, i * 2 + 1);
如果查询的是区间最大值:
else if (l <= mid && r > mid) return max(query(l, r, i * 2), query(l, r, i * 2 + 1));
如果查询的是区间最小值:
else if (l <= mid && r > mid) return min(query(l, r, i * 2), query(l, r, i * 2 + 1));
- 当前区间
∩
\cap
∩ 目标区间
⊆
\subseteq
⊆ 当前区间的左子区间
这种情况下递归的返回值应该是左子区间的返回值:
else if (r <= mid) return query(l, r, i * 2);
- 当前区间
∩
\cap
∩ 目标区间
⊆
\subseteq
⊆ 当前区间的右子区间
这种情况下递归的返回值应该是右子区间的返回值:
else if (l > mid) return query(l, r, i * 2 + 1);
综上所述,区间查询的代码如下,
如果是区间和:
long long query(int l, int r, int i)
{
int mid = (c[i].l + c[i].r) / 2;
if (c[i].lazy != 0) pushdown(i);
if (l <= c[i].l && r >= c[i].r) return c[i].sum;
else if (l <= mid && r > mid) return query(l, r, i * 2) + query(l, r, i * 2 + 1);
else if (r <= mid) return query(l, r, i * 2);
else if (l > mid) return query(l, r, i * 2 + 1);
}
如果是区间最大值:
long long query_max(int l, int r, int i)
{
int mid = (c[i].l + c[i].r) / 2;
if (c[i].lazy != 0) pushdown(i);
if (l <= c[i].l && r >= c[i].r) return c[i].max;
else if (l <= mid && r > mid) return max(query_max(l, r, i * 2), query_max(l, r, i * 2 + 1));
else if (r <= mid) return query_max(l, r, i * 2);
else if (l > mid) return query_max(l, r, i * 2 + 1);
}
如果是区间最小值:
long long query_min(int l, int r, int i)
{
int mid = (c[i].l + c[i].r) / 2;
if (c[i].lazy != 0) pushdown(i);
if (l <= c[i].l && r >= c[i].r) return c[i].min;
else if (l <= mid && r > mid) return min(query_min(l, r, i * 2), query_min(l, r, i * 2 + 1));
else if (r <= mid) return query_min(l, r, i * 2);
else if (l > mid) return query_min(l, r, i * 2 + 1);
}
封装成类:
若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
public:
int l, r;
long long sum, max, min, lazy;
Node()
{
l = 0;
r = 0;
sum = 0;
max = 0;
min = 0;
lazy = 0;
}
};
class SegTree : vector<Node>
{
public:
int size;
explicit SegTree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size * 4, Node());
build(1, size, 1);
}
void update(int l, int r, long long val)//区间修改
{
if (l < 1 || r < l || r > size) return;
loc_l = l;
loc_r = r;
this->val = val;
_update(1);
}
void update(int loc, long long val)//重载一个单点修改
{
if (loc < 1 || loc > size) return;
loc_l = loc_r = loc;
this->val = val;
_update(1);
}
long long query(int l, int r)//区间查询
{
if (l < 1 || r < l || r > size) return 0;
loc_l = l;
loc_r = r;
return _query(1);
}
long long query(int loc)//重载一个单点查询
{
if (loc < 1 || loc > size) return 0;
loc_l = loc_r = loc;
return _query(1);
}
long long query_max(int l, int r)//区间最大值查询
{
if (l < 1 || r < l || r > size) return 0;
loc_l = l;
loc_r = r;
return _query_max(1);
}
long long query_min(int l, int r)//区间最小值查询
{
if (l < 1 || r < l || r > size) return 0;
loc_l = l;
loc_r = r;
return _query_min(1);
}
private:
int loc_l, loc_r;
long long val;
void build(int l, int r, int loc)
{
(*this)[loc].l = l;
(*this)[loc].r = r;
if (l == r) return;
build(l, (l + r) / 2, loc * 2);
build((l + r) / 2 + 1, r, loc * 2 + 1);
}
void pushup(int i)
{
(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
}
void _update(int i)
{
if ((*this)[i].l == (*this)[i].r)
{
(*this)[i].max += val;
(*this)[i].min += val;
(*this)[i].sum += val;
}
else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
else
{
int mid = ((*this)[i].l + (*this)[i].r) / 2;
if (mid >= loc_l) _update(i * 2);
if (mid < loc_r) _update(i * 2 + 1);
pushup(i);
}
}
void pushdown(int i)
{
(*this)[i].sum += (*this)[i].lazy * ((*this)[i].r - (*this)[i].l + 1);
(*this)[i].max += (*this)[i].lazy;
(*this)[i].min += (*this)[i].lazy;
if ((*this)[i].l != (*this)[i].r)
{
(*this)[i * 2].lazy += (*this)[i].lazy;
(*this)[i * 2 + 1].lazy += (*this)[i].lazy;
}
(*this)[i].lazy = 0;
}
long long _query(int i)
{
int mid = ((*this)[i].l + (*this)[i].r) / 2;
if ((*this)[i].lazy != 0) pushdown(i);
if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].sum;
else if (loc_l <= mid && loc_r > mid) return _query(i * 2) + _query(i * 2 + 1);
else if (loc_r <= mid) return _query(i * 2);
else if (loc_l > mid) return _query(i * 2 + 1);
}
long long _query_max(int i)
{
int mid = ((*this)[i].l + (*this)[i].r) / 2;
if ((*this)[i].lazy != 0) pushdown(i);
if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].max;
else if (loc_l <= mid && loc_r > mid) return max(_query_max(i * 2), _query_max(i * 2 + 1));
else if (loc_r <= mid) return _query_max(i * 2);
else if (loc_l > mid) return _query_max(i * 2 + 1);
}
long long _query_min(int i)
{
int mid = ((*this)[i].l + (*this)[i].r) / 2;
if ((*this)[i].lazy != 0) pushdown(i);
if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].min;
else if (loc_l <= mid && loc_r > mid) return min(_query_min(i * 2), _query_min(i * 2 + 1));
else if (loc_r <= mid) return _query_min(i * 2);
else if (loc_l > mid) return _query_min(i * 2 + 1);
}
};
线段树的空间优化
未完待续…
多维线段树(以二维线段树为例)
未完待续…
线段树的应用与习题
未完待续…