线段树
一.概念
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。(来自百度)
二.代码分段解析
代码中涉及许多左移和右移操作,“>>1”="/2","<<1"="*2",区别就是速度比一般的乘除更快
1.定义
using namespace std;
struct tree{
ll sum;
ll lazy;//懒标记
}node[maxn<<2];//=[maxn*4],上面提到过数组开4N以免越界
一般用结构体来定义,lazy标记主要用来标记运算过程
2.构造树
void build(int rt,int l,int r)
{
node[rt].lazy=0;
if(l==r)
{
node[rt].sum=a[l];//找到更新的点,给该点赋上初值
return ;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);//递归继续构造左子树
build(rt<<1|1,mid+1,r);//右子树
update(rt);//更新当前节点
}
3.更新
void update(int rt)
{
node[rt].sum=node[rt<<1].sum+node[rt<<1|1].sum;
}
这只是求和的一种更新方式,不同题需要不同的更新
如找最大值:
void update(int rt)
{
node[rt].maxx=max(node[rt<<1].maxx,node[rt<<1|1].maxx);
}
4.改变
(1)区间修改
void change(int rt,int l,int r,int pl,int pr,ll val)
//根节点,区间最左端和最右端,要改变的区间最左端和最右端,改变的值
{
if(pl==l&&pr==r)//找到完全重合的区间
{
node[rt].sum+=val*(pr-pl+1);
node[rt].lazy+=val;//将已经改变的区间进行懒标记,但在往下推后要把这个懒标记去除
return ;
}
pushdown(rt,l,r);
int mid=(r+l)>>1;
if(pr<=mid)
change(rt<<1,l,mid,pl,pr,val);//区间全在左子树
else if(pl>mid)
change(rt<<1|1,mid+1,r,pl,pr,val);//区间全在右子树
else//区间在左子树,右子树都有
{
change(rt<<1,l,mid,pl,mid,val);
change(rt<<1|1,mid+1,r,mid+1,pr,val);
}
update(rt);//更新当前结点
}
5.下推lazy标记
void pushdown(int rt,int l,int r)
{
if(node[rt].lazy==0)
return;
int chl=rt<<1;
int chr=rt<<1|1;//=rt*2+1
int mid=(l+r)>>1;
node[chl].sum+=node[rt].lazy*(mid-l+1);//给rt的左子节点重新赋值
node[chr].sum+=node[rt].lazy*(r-mid);//给rt的右子节点重新赋值
node[chl].lazy+=node[rt].lazy;//将lazy标记下传到子节点
node[chr].lazy+=node[rt].lazy;
node[rt].lazy=0;//往下推后要去掉当前节点lazy标记
}
6.询问
ll query(int rt,int l,int r,int pl,int pr)
{
if(l==pl&&r==pr)//找到查询区间,如果区间完全重合,返回
return node[rt].sum;
pushdown(rt,l,r);//没有返回就继续往下找
int mid=(l+r)>>1;
if(pr<=mid)
return query(rt<<1,l,mid,pl,pr);//查询区间全在左子树上
else if(pl>mid)
return query(rt<<1|1,mid+1,r,pl,pr);//查询区间全在右子树上
else
return query(rt<<1,l,mid,pl,mid)+query(rt<<1|1,mid+1,r,mid+1,pr);
}
如果代码还没理解透彻,可以自己跟着模拟看看(画个树状图,列个表格),看看代码是怎么实现的
自己也还在学习,持续更新中……