什么是线段树?
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
性质:对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。线段树单点更新模板
//线段树的结构
struct node
{
int l, r, val;
}segTree[N * 3];
int arr[N];
//更新父节点
void pushup(int i)
{
segTree[i].val = segTree[i * 2].val + segTree[i * 2 + 1].val;//决定父节点的值代表的是什么
}
//建立线段树
void build(int l, int r, int i)
{
segTree[i].l = l;//存入此节点的范围
segTree[i].r = r;
if(l == r)
{
segTree[i].val = arr[l];
return ;
}
int mid = (l + r) / 2;
build(l, mid, i * 2);//构建左子树
build(mid + 1, r, i * 2 + 1);//构建右子树
pushup(i);
}
//单节点更新
void update(int x, int addval, int i)
{
int l = segTree[i].l;
int r = segTree[i].r;
if(l == x && r == x)
{
segTree[i].val += addval;
return ;
}
int mid = (l + r) / 2;
if(x <= mid) update(x, addval, i * 2);
else update(x, addval, i * 2 + 1);
pushup(i);
}
//查询区间的值
int query(int l, int r, int i)
{
if(segTree[i].l == l && segTree[i].r == r)
{
return segTree[i].val;
}
int mid = (segTree[i].l + segTree[i].r) / 2;
if(r <= mid) return query(l, r, i * 2);
else if(l > mid) return query(l, r, i * 2 + 1);
else return query(l, mid, i *2) + query(mid + 1, r, i * 2 + 1);
}
线段树区间更新模板 (lazy 数组 懒人标记 + pushdown)
//线段树的结构
struct node
{
int l, r, val;
int lazy, len;
}segTree[N * 3];
int arr[N];
//更新父节点
void pushup(int i)
{
segTree[i].val = segTree[i * 2].val + segTree[i * 2 + 1].val;//决定父节点的值代表的是什么
}
//更新左右孩子
void pushdown(int i)
{
//更新左孩子
segTree[i * 2].lazy += segTree[i].lazy;
segTree[i * 2].val += segTree[i * 2].len * segTree[i].lazy;
//更新右孩子
segTree[i * 2 + 1].lazy += segTree[i].lazy;
segTree[i * 2 + 1].val += segTree[i * 2 + 1].len * segTree[i].lazy;
//父节点归零
segTree[i].lazy = 0;
}
//建立线段树
void build(int l, int r, int i)
{
segTree[i].l = l;//存入此节点的范围
segTree[i].r = r;
segTree[i].lazy = 0;
segTree[i].len = (r - l) + 1;
if(l == r)
{
segTree[i].val = arr[l];
return ;
}
int mid = (l + r) / 2;
build(l, mid, i * 2);//构建左子树
build(mid + 1, r, i * 2 + 1);//构建右子树
pushup(i);
}
//区间点更新
void update(int l, int r, int addval, int i)
{
if(segTree[i].l == l && segTree[i].r == r)
{
segTree[i].lazy += addval;
segTree[i].val += segTree[i].len * addval;
return ;
}
if(segTree[i].lazy) pushdown(i);
int mid = (segTree[i].l + segTree[i].r) / 2;
if(r <= mid) update(l, r, addval, i * 2);
else if(l > mid) update(l, r, addval, i * 2 + 1);
else
{
update(l, mid, addval, i *2);
update(mid + 1, r, addval, i * 2 + 1);
}
pushup(i);
}
//查询区间的值
int query(int l, int r, int i)
{
if(segTree[i].l == l && segTree[i].r == r)
{
return segTree[i].val;
}
if(segTree[i].lazy) pushdown(i);
int mid = (segTree[i].l + segTree[i].r) / 2;
if(r <= mid) return query(l, r, i * 2);
else if(l > mid) return query(l, r, i * 2 + 1);
else return query(l, mid, i *2) + query(mid + 1, r, i * 2 + 1);
}
- 区间更新
区间更新:是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn),例如当我们要更新区间[0,3]内的叶子节点时,需要更新出了叶子节点3,9外的所有其他节点。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。
延迟标记:每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。