对于维护区间内的信息,我们可使用RMQ,但这种做法的缺点是无法快速修改,而线段树这种数据结构则可以实现实时的查询、修改(单点、区间)。
原理:
建树:
只需要遍历到每个叶子节点时赋值,回溯过程中再对树进行调整
也可以进行N次单点修改,一般来说对总的时间复杂度影响不大
void build(int l,int r,int p)
{
int mid=(l+r)>>1;
if(l==r)
{
s[p]=v[l];
return;
}
build(l,mid,lson);
build(mid+1,r,rson);
}
区间查询:
若想查询某一区间信息,我们可从根节点遍历。当遍历到的节点被查询区间完全覆盖的时候,便返回信息。
在建树时,我们可以选择用堆式结构,或者动态开点。
如要查询1~8的信息:
节点1~10左右节点均有1~6的信息,向下查询
节点1~5被查询区间完全包含,返回该点信息
节点6~10的左区间包含查询区间信息,向左遍历
节点6~8被查询区间完全包含,返回该点区间信息
最后获得1~5、6~8的信息,再进行整合。
如求和:
int query_s(int l,int r,int x,int y,int p)
{
int mid=(l+r)>>1;
if(x<=l&&y>=r) return s[p];
int ret=0;
if(x<=mid) ret+=query_s(l,mid,x,y,lson);
if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
return ret;
}
此段代码中,我们选择把该区间的左右范围传入函数,这样可以节省空间与时间。
若使用堆式结构,那么lson = p<<1 , rson = p << 1 | 1 (p为该点的位置)
若使用动态开点,lson rson则为数组
单点修改:
对于单点修改,首先,我们要找到该点在线段树中对应的位置,再将其权值修改,在回溯的过程中修改沿途的节点。
若使用动态开点,我们则需要注意左右儿子节点是否被开出来过,这里我们用传引用的方式对p进行修改。
动态开点版:
void update(int l,int r,int x,int v,int &p)
{
int mid=(l+r)>>1;
if(p==0) p=++cnt;
if(l==r)
{
m[p]=v;
s[p]=v;
return;
}
if(x<=mid) update(l,mid,x,v,lson[p]);
else update(mid+1,r,x,v,rson[p]);
m[p]=max(m[lson[p]],m[rson[p]]);
s[p]=s[lson[p]]+s[rson[p]];
}
堆式结构版:
void update(int l,int r,int x,int v,int p)
{
int mid=(l+r)>>1;
if(l==r)
{
m[p]=v;
s[p]=v;
return;
}
if(x<=mid) update(l,mid,x,v,lson);
else update(mid+1,r,x,v,rson);
m[p]=max(m[lson],m[rson]);
s[p]=s[lson]+s[rson];
}
区间修改:
我们可以看出,线段树可以很方便的进行单点修改与区间查询,但区间修改却略显麻烦。
暴力来做,区间修改就是多次单点修改,那么单词修改时间复杂度就退化成了NlogN,十分不理想。
在这里,我们加入lazy数组进行优化
顾名思义,lazy数组可以减少一次区间修改的操作数(就是懒)
原理:
对于区间修改,我们可以不完成这个操作,而是像区间查询一样,把要修改的区间分割成线段树上对应的若干个节点,在这些节点的lazy上加上我们想要的值(这里讲解的是区间加和,覆盖同理)。等到查询的时候,我们再将lazy数组向下推,并且进行赋值之类的操作。
int lz[maxn<<2];
int tr[maxn<<2];
int n;
int v[maxn];