线段树模板总结

线段树:
  • 什么是线段树?

    线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。 
    性质:对于线段树中的每一个非叶子节点[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的标记。


猜你喜欢

转载自blog.csdn.net/memory_98/article/details/80732784