线段树
线段树定义:
是一个平衡的二叉树,每个内部结点v表示基本区间I,其被分成用于v的两个子结点的区间Il和Ir,分别为它的左右子树的基本区间,区间相对于线段的端点被分割。
其实它就是长这个样子:每个结点设置一个结点值(我们这里看求和的线段树),代表它的左右子树结点值的和,同时它代表了它左右子树的线段的和。(我代码里面的左端点是从0开始的)
线段树类的定义:
它包括一个节点值,一个区间,一个lazy标记以及几个操作。其实主要是用过这样一个LineSegmentTree的数组来表示这棵树的,其中tree[i]的子结点我表示为tree[2*i + 1]和tree[2*i + 2],
下标是从0开始的。
class LineSegmentTreeNode
{
public:
//线段树节点值
long long ele;
//该节点对应的区间
int L;
int R;
//lazy标记
long long lazy;
//建立线段树
void BuildTree(LineSegmentTreeNode* tree,int l,int r,int node);
//点更新
void UpdatePoint(LineSegmentTreeNode* tree,int index,int l,int r,int n,int node);
//区间更新
void UpdateInterval(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R,int n);
//下调
void PushDown(LineSegmentTreeNode* tree,int l,int r,int node);
//区间查询,返回一个区间中所有值的和
long long IntervalQuery(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R);
LineSegmentTree()
{
}
LineSegmentTreeNode operator+(const LineSegmentTreeNode& a)
{
LineSegmentTreeNode c;
c.ele = ele + a.ele;
c.L = min(L,a.L);
c.R = max(R,a.R);
return c;
}
};
构建线段树
我是想通过输入具体n个结点值的方式来构建线段树,时间复杂度(O(n))
void LineSegmentTreeNode::BuildTree(LineSegmentTreeNode* tree,int l,int r,int node)
{
//找到了叶节点
if(l == r)
{
cin>>tree[node].ele;
tree[node].L = l;
tree[node].R = r;
tree[node].lazy = 0;
return;
}
int m = (l+r)/2;
//递归构建左右子树
BuildTree(tree,l,m,node*2+1);
BuildTree(tree,m + 1,r,node*2+2);
//利用左右子树的值对节点赋值,初始化
tree[node] = tree[node*2+1] + tree[node*2+2];
//初始化lazy标签
tree[node].lazy = 0;
}
点更新
对于某一个叶结点如果它的值加了一个n,那么它的父结点以及众多祖先结点也要加上对应的值,代码如下:
//l,r表示树中待更新的区间,node表示递归过程中要被更新的点,index表示可以要被更新叶节点的索引
void LineSegmentTreeNode::UpdatePoint(LineSegmentTreeNode* tree,int index,int l,int r,int n,int node)
{
//找到了叶节点
if(l == r)
{
tree[node].ele+=n;
return;
}
int m = (l + r)/2;
if(index <= m)
UpdatePoint(tree,index,l,m,n,node*2+1);
else
UpdatePoint(tree,index,m + 1,r,n,node*2+2);
tree[node] = tree[node*2 + 1] + tree[node*2 + 2];
}
lazy标记的作用
对于点更新,直接递归的进行更新,时间复杂度是O(logn),但是如果对于一个区间内所有点都进行更新,就需要消耗很多的时间了,那么就需要一个东西(lazy)暂时存储这个区间内点的状态变化,不用的时候就不会再对这个区间内的其他的点全部进行更新,当要用某个点的时候,从lazy中取出要用的值,对它的区间内到这个点为止的点进行更新即可。也就是区间更新的时候,只需要更一次这个区间对应的结点和这个区间对应结点的两个子结点,剩下子结点的变化会存在lazy中,不用的时候不动,用的时候才从lazy中进行取值。
取值的时候会执行PushDown函数
//利用lazy标记获取点的更新值
void LineSegmentTreeNode::PushDown(LineSegmentTreeNode* tree,int l,int r,int node)
{
if(tree[node].lazy)
{
//lazy一次只往下更新一层,节点值和下层的lazy都要更新
tree[node * 2 + 1].lazy += tree[node].lazy;
tree[node * 2 + 2].lazy += tree[node].lazy;
int m = (l + r)/2;
tree[node * 2 + 1].ele += (m - l + 1)*tree[node].lazy;
//r - (m + 1) + 1 = r-m
tree[node * 2 + 2].ele += (r - m)*tree[node].lazy;
tree[node].lazy = 0;
}
}
区间更新
对某个区间内所有的点都进行一个加某个值的操作。
//l,r表示线段树中可能被更新的区间,L,R表示要被更新的区间(目标区间),node表示递归过程中的点
void LineSegmentTreeNode::UpdateInterval(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R,int n)
{
if(L <= l && r <= R)
{
//该区间是目标区间的子集,对应节点更新并打上lazy标记即可,无需在这个时候下调
tree[node].ele += n*(r - l + 1);
tree[node].lazy += n;
return;
}
//本节点有可能有lazy值,也就是之前被更新过,所以先更新本节点值
PushDown(tree,l,r,node);
int m = (l + r)/2;
//该节点区间的中点如果位于目标区间左端点的右边或者右端点的左边,那么它就有可能需要被更新
if(m >= L)
UpdateInterval(tree,node * 2 + 1,l,m,L,R,n);
if(m + 1 <= R)
UpdateInterval(tree,node * 2 + 2,m + 1,r,L,R,n);
tree[node] = tree[node * 2 + 1] + tree[node * 2 + 2];
}
求某个区间中所有叶子结点值的和
//输出l,r区间中所有叶节点的值的和
long long LineSegmentTreeNode::IntervalQuery(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R)
{
if(l == r)
{
return tree[node].ele;
}
int m = (l + r)/2;
long long sum = 0;
if(m >= L)
sum += IntervalQuery(tree,node * 2 + 1,l,m,L,R);
if(m + 1 <= R)
sum += IntervalQuery(tree,node * 2 + 2,m + 1,r,L,R);
return sum;
}
完整的源代码如下:
#include<bits/stdc++.h>
using namespace std;
class LineSegmentTreeNode
{
public:
//线段树节点值
long long ele;
//该节点对应的区间
int L;
int R;
//lazy标记
long long lazy;
//建立线段树
void BuildTree(LineSegmentTreeNode* tree,int l,int r,int node);
//点更新
void UpdatePoint(LineSegmentTreeNode* tree,int index,int l,int r,int n,int node);
//区间更新
void UpdateInterval(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R,int n);
//下调
void PushDown(LineSegmentTreeNode* tree,int l,int r,int node);
//区间查询,返回一个区间中所有值的和
long long IntervalQuery(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R);
LineSegmentTree()
{
}
LineSegmentTreeNode operator+(const LineSegmentTreeNode& a)
{
LineSegmentTreeNode c;
c.ele = ele + a.ele;
c.L = min(L,a.L);
c.R = max(R,a.R);
return c;
}
};
int main()
{
int n;
cin>>n;
const int maxN = 4*n;
LineSegmentTreeNode* tree = new LineSegmentTreeNode[maxN];
tree[0].BuildTree(tree,0,n-1,0);
//tree[0].UpdatePoint(tree,7,0,0,1,0);
tree[0].UpdateInterval(tree,0,0,7,4,7,2);
//cout<<tree[6].ele<<endl;
//如果你发现最后四个数以及它上一层的两个节点没有变,那你一定是还没理解lazy的作用,把我下面几行注释打开,再试一试就明白了
//tree[0].PushDown(tree,4,7,2);
//tree[0].PushDown(tree,6,7,6);
//tree[0].PushDown(tree,4,5,5);
//cout<<tree[13].ele<<" "<<tree[14].ele<<endl;
//cout<<tree[0].IntervalQuery(tree,2,4,7,4,5)<<endl;
return 0;
}
void LineSegmentTreeNode::BuildTree(LineSegmentTreeNode* tree,int l,int r,int node)
{
//找到了叶节点
if(l == r)
{
cin>>tree[node].ele;
tree[node].L = l;
tree[node].R = r;
tree[node].lazy = 0;
return;
}
int m = (l+r)/2;
//递归构建左右子树
BuildTree(tree,l,m,node*2+1);
BuildTree(tree,m + 1,r,node*2+2);
//利用左右子树的值对节点赋值,初始化
tree[node] = tree[node*2+1] + tree[node*2+2];
//初始化lazy标签
tree[node].lazy = 0;
}
//l,r表示树中待更新的区间,node表示递归过程中要被更新的点,index表示可以要被更新叶节点的索引
void LineSegmentTreeNode::UpdatePoint(LineSegmentTreeNode* tree,int index,int l,int r,int n,int node)
{
//找到了叶节点
if(l == r)
{
tree[node].ele+=n;
return;
}
int m = (l + r)/2;
if(index <= m)
UpdatePoint(tree,index,l,m,n,node*2+1);
else
UpdatePoint(tree,index,m + 1,r,n,node*2+2);
tree[node] = tree[node*2 + 1] + tree[node*2 + 2];
}
//利用lazy标记获取点的更新值
void LineSegmentTreeNode::PushDown(LineSegmentTreeNode* tree,int l,int r,int node)
{
if(tree[node].lazy)
{
//lazy一次只往下更新一层,节点值和下层的lazy都要更新
tree[node * 2 + 1].lazy += tree[node].lazy;
tree[node * 2 + 2].lazy += tree[node].lazy;
int m = (l + r)/2;
tree[node * 2 + 1].ele += (m - l + 1)*tree[node].lazy;
//r - (m + 1) + 1 = r-m
tree[node * 2 + 2].ele += (r - m)*tree[node].lazy;
tree[node].lazy = 0;
}
}
//l,r表示线段树中可能被更新的区间,L,R表示要被更新的区间(目标区间),node表示递归过程中的点
void LineSegmentTreeNode::UpdateInterval(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R,int n)
{
if(L <= l && r <= R)
{
//该区间是目标区间的子集,对应节点更新并打上lazy标记即可,无需在这个时候下调
tree[node].ele += n*(r - l + 1);
tree[node].lazy += n;
return;
}
//本节点有可能有lazy值,也就是之前被更新过,所以先更新本节点值
PushDown(tree,l,r,node);
int m = (l + r)/2;
//该节点区间的中点如果位于目标区间左端点的右边或者右端点的左边,那么它就有可能需要被更新
if(m >= L)
UpdateInterval(tree,node * 2 + 1,l,m,L,R,n);
if(m + 1 <= R)
UpdateInterval(tree,node * 2 + 2,m + 1,r,L,R,n);
tree[node] = tree[node * 2 + 1] + tree[node * 2 + 2];
}
//输出l,r区间中所有叶节点的值的和
long long LineSegmentTreeNode::IntervalQuery(LineSegmentTreeNode* tree,int node,int l,int r, int L, int R)
{
if(l == r)
{
return tree[node].ele;
}
int m = (l + r)/2;
long long sum = 0;
if(m >= L)
sum += IntervalQuery(tree,node * 2 + 1,l,m,L,R);
if(m + 1 <= R)
sum += IntervalQuery(tree,node * 2 + 2,m + 1,r,L,R);
return sum;
}