自从初中学习了就再也没用过,上了大学发现全忘光了,特此重新学习记录学习笔记。
学习参考:https://www.cnblogs.com/fusiwei/p/11301700.html
首先明确线段树的作用:用于区间统计,统计区间的某一特性,这一特性可以是区间求和,区间最值。
线段树是二叉树结构,一个节点代表的是一个区间,根节点1是全部区间
假设父节点为x,则左儿子为2x,右儿子为2x+1
线段树可以进行非常多操作,以及拓展衍生出的多种用法,这里先暂时只学习简单线段树的用法:
简单线段树支持:单点查询、单点修改、区间查询、区间修改。
相比于树状数组,线段树更强大,树状数组可以解决的问题线段树都可以解决,但是线段树可以解决的问题树状数组不一定可以解决,但是,树状数组的代码复杂度与常数较低,所以在写代码的过程中要平衡考虑,不单一看待。
实现线段树:
有两种,一种是结构体方式,维护节点的编号、节点所表示的区间,用一维数组实现的。
线段树的时间复杂度是
模板1:简单线段树(实现区间修改加值、区间查询数值和)
一般来说,区间总长度为n,线段树空间开 4n 为宜。
建树
const int N = 1e5+5;
#define ls (pos<<1)
#define rs (pos<<1|1)
int Tree[4*N];
void build_tree(int pos,int l,int r)//pos是数组下标,l,r是区间
{
if(l==r){//叶子节点
Tree[pos] = A[l];//赋值
return ;
}
int mid = (l+r)>>1;
build_tree(ls,l,mid);
build_tree(rs,mid+1,r);//分裂区间,向下建树
Tree[pos] = Tree[ls]+Tree[rs];//向上更新
return ;
}
当程序不给出初始值时,自然不需要建树操作。
区间修改
lazy标记的理解及其pushdown操作:
为何叫lazy?如果我们需要修改区间[x,y]内容,而当前递归的点表示的区间[a,b]是[x,y]的子区间,则我们不用继续往下深入递归,而是直接修改该点,放下lazy标记修改整个区间的属性(这也就是为什么线段树快的原因,如果递归到叶子节点,时间复杂度比还大)。先对线段树该点加上修改的区间长度*修改值(也就是对这个点区间加上全部的值),再放下一个lazy标记,加上修改的值。
为什么pushdown?因为如果只修改大区间,小区间并没有得到修改,所以需要在我们后面递归往下的时候进行pushdown操作。pushdown的操作就是线段树提高效率的核心!
void mark(int pos,int l,int r,int k)
{
Tree[pos] +=(r-l+1) * k;//大区间增加区间总值
lazy[pos] +=k;//lazy标记
return ;
}
void push_down(int pos,int l,int r)
{
int mid = (l+r)>>1;
mark(ls,l,mid,lazy[pos]);
mark(rs,mid+1,r,lazy[pos]);//向下增加积累的lazy值
lazy[pos] = 0;//清空lazy标记
}
void update(int pos,int l,int r,int x,int y,int k)
{
//在x-y区间增加k
if(x<=l&&r<=y){
mark(pos,l,r,k);
return ;
}
int mid = (l+r)>>1;
push_down(pos,l,r);//向下递归前要释放lazy标记
if(x<=mid)
update(ls,l,mid,x,y,k);
if(y>mid)
update(rs,mid+1,r,x,y,k);
Tree[pos] = Tree[ls] + Tree[rs];//更新
return ;
}
查询区间
与修改的思路保持一致,不断递归返回子树在所求区间范围的值。
int Query(int pos,int l,int r,int x,int y)
{
int ret = 0;
if(x<=l&&r<=y) return Tree[pos];
int mid = (l+r)>>1;
if(x<=mid)
ret += Query(ls,l,mid,x,y);
if(y>mid)
ret += Query(rs,mid+1,r,x,y);
return ret;
}