目录
制作不易,点个赞再走吧
什么是线段树
线段树(Segment Tree)几乎是算法竞赛最常用的数据结构了,它主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可以实现 O(logn) 的区间修改,还可以同时支持多种操作(加、乘),更具通用性。
线段树就是一棵二叉平衡树,根节点代表某一段区间和,越往下区间越小,直到区间为的长度为1的时候结束(叶子节点)。
我们可以通过模板题来了解线段树:
线段树的作用
线段树的建立
线段树的建立可以通过依次遍历左右两边的子树,知道找到叶子节点,然后将对应的数值插入该节点上(注意一定要先遍历左子树,然后遍历右子树),插入后,然后将左右子树的值传给根节点,依次进行,那么我们所得到的线段树就是,根节点等于左右节点的和。
那么我们现在讨论标号的问题,我们所建立的线段树对应的标号是怎样的?
我们是这样进行标号的,每遍历到一个点 u ,那么他的左子树就是 u * 2,那么它的右子树就是u * 2+1。
那么它对应的区间又是怎样的呢?
我们就是这样进行对区间进行分配的。
那么区间求和又是怎样一回事呢?
那么这些就是线段树的基本结构,详细细节,请看代码。
代码实现:
void pushup(int u)
{
tree[u] = tree[u<<1] + tree[u<<1|1];
}
// 建树
void buile(int l,int r,int u)
{
if(l == r) tree[u] = arr[l];
else {
int mid = (l + r) >> 1;
buile(l,mid,u<<1);
buile(mid+1,r,u<<1|1);
pushup(u);
}
}
区间修改
我们想进行区间修改的时候,我们需要先找到我们需要修改的区间位置,然后再进行修改,查找的思路请自己观看代码进行了解,这里就不多进行描述。
值得注意的是,如果我们每一次都直接对区间内的每一个叶子进行修改,那么时间复杂度就会大大提升,那么我们就想一种方法进行优化。我们想可以先不进行对每一个叶子进行修改,先对我们需要找的区间的范围进行修改,然后我们将这个区间对应的标号标记一下,并存下需要加的值,然后当下一次进行遍历,遍历到该点的左右节点的时候,就将该点标记的值加在下子树中,然后取消标记,这样,我们就不需要每次都遍历区间对应的每个点了。我们将这个方法叫做“lazy标记(懒标记)”
想必,大家看到这么多字都看蒙了,那么结合图来讲解大家一定喜欢。
这样就完成了区间修改。
实现代码:
//区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
if(cl > r || cr < l) return ;
if(cl >= l && cr <= r)
{
tree[u] += (cr - cl + 1) * x;
if(cr > cl) lazy[u] += x;
}
else {
int mid = (cl + cr) / 2;
lazy[u<<1] += lazy[u];
lazy[u<<1|1] += lazy[u];
tree[u<<1] += lazy[u] * (mid -cl+1);
tree[u<<1|1] += lazy[u] * (cr-mid);
lazy[u] = 0;
update(l,r,x,2*u,cl,mid);
update(l,r,x,2*u+1,mid+1,cr);
pushup(u);
}
}
当然我们也可以将lazy的计算放入一个函数中,分块解决,可以更好理解
实现代码:
void push_down(int u,int len)
{
lazy[u<<1] += lazy[u];
lazy[u<<1|1] += lazy[u];
tree[u<<1] += lazy[u] * (len-len/2);
tree[u<<1|1] += lazy[u] * (len/2);
lazy[u] = 0;
}
// 区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
if(cl > r || cr < l) return ;
if(cl >= l && cr <= r)
{
tree[u] += (cr - cl + 1) * x;
if(cr > cl) lazy[u] += x;
}
else {
int mid = (cl + cr) / 2;
push_down(u,cr-cl+1);
update(l,r,x,2*u,cl,mid);
update(l,r,x,2*u+1,mid+1,cr);
pushup(u);
}
}
区间查询
根据上述对于线段树的解释,想必大家对与线段树有了基本的了解,那么对与这个区间查询,就更好理解了,
区间查询就是和区间修改差不多,一个是找了一个值,然后进行修改那个值,而区间查询则是返回这个值,也是使用递归的方式进行计算的。值得一提的是,当我们进行区间查询的时候也是需要进行lazy值的处理,因为,在区间修改的时候不一定能遍历到这个我们需要查询的这个点,所以,我们进行区间查询的时候也是需要进行lazy的处理。
其他的操作就和区间修改差不多了,这里就不多描述了,想必聪明的你一定能一眼看懂两者的区别和求区间和的实现方式。
实现代码
void push_down(int u,int len)
{
lazy[u<<1] += lazy[u];
lazy[u<<1|1] += lazy[u];
tree[u<<1] += lazy[u] * (len-len/2);
tree[u<<1|1] += lazy[u] * (len/2);
lazy[u] = 0;
}
// 区间查询
int query(int l,int r,int u,int cl,int cr) {
if(cl > r || cr < l) return 0;
else if(cl >= l && cr <= r)
{
return tree[u];
}
else {
int mid = (cl + cr) /2;
push_down(u,cr-cl+1);
return query(l,r,u<<1,cl,mid) + query(l,r,u<<1|1,mid+1,cr);
}
}
线段树的代码综合实现
该代码对应的就是上面给出的链接里面的题,结合题目理解代码会事半功倍哈。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 100005;
int n,m;
int tree[4*N];
int arr[N];
int lazy[4*N];
void push_down(int u,int len)
{
lazy[u<<1] += lazy[u];
lazy[u<<1|1] += lazy[u];
tree[u<<1] += lazy[u] * (len-len/2);
tree[u<<1|1] += lazy[u] * (len/2);
lazy[u] = 0;
}
void pushup(int u)
{
tree[u] = tree[u<<1] + tree[u<<1|1];
}
// 建树
void buile(int l,int r,int u)
{
if(l == r) tree[u] = arr[l];
else {
int mid = (l + r) >> 1;
buile(l,mid,u<<1);
buile(mid+1,r,u<<1|1);
pushup(u);
}
}
// 区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
if(cl > r || cr < l) return ;
if(cl >= l && cr <= r)
{
tree[u] += (cr - cl + 1) * x;
if(cr > cl) lazy[u] += x;
}
else {
int mid = (cl + cr) / 2;
push_down(u,cr-cl+1);
update(l,r,x,2*u,cl,mid);
update(l,r,x,2*u+1,mid+1,cr);
pushup(u);
}
}
// 区间查询
int query(int l,int r,int u,int cl,int cr) {
if(cl > r || cr < l) return 0;
else if(cl >= l && cr <= r)
{
return tree[u];
}
else {
int mid = (cl + cr) /2;
push_down(u,cr-cl+1);
return query(l,r,u<<1,cl,mid) + query(l,r,u<<1|1,mid+1,cr);
}
}
signed main (void)
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
scanf("%lld",&arr[i]);
}
buile(1,n,1);
while(m--)
{
int num;
scanf("%lld",&num);
if(num == 1){
int a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
update(a,b,c,1,1,n);
}
else if(num == 2){
int a,b;
scanf("%lld%lld",&a,&b);
cout << query(a,b,1,1,n) << endl;
}
}
return 0;
}