线段树

区间查询:

询问某些区间的某些性质(极值、求和等);

区间更新:

某些操作影响了某段区间(统一加一个数等);

三个问题:

更新点,查询区间;

更新区间,查询点;

更新区间,查询区间;

时间空间复杂度O(log2N);

线段树的本质是二叉树,不同于其他的二叉树,线段树的每个节点存储的是一段区间、记录的是这个区间的信息。

对于长度为5的数组a[1]~a[5]

                                                                      [1 5]

                                                           [1 3]                 [4 5]

                                                    [1 2]      [3 3]         [4 4] [5 5]

                                               [1 1] [2 2]       

对于任意的非叶子节点,若该区间为[L,R],则左二子为[L,(L+R)/2],右儿子为[(L+R)/2+1,R];

                                                                      

                                                              1 5 4 1 6(sum=17,max=6)

                            1 5 4(max=5,sum=10)                                            1 6(max=6,sum=7)

            1 5(max=5,sum=6)           4(max=4,sum4)              1(max=1,sum=1)  6(max=6,sum7)

   1(max=1,sum=1)    5(max=5,sum=5)

修改a[2]=3,修改之后,再做一次pushup,pushuup是自下而上的操作,修改是自上而下,在由下跟新到上;

 

                                                                                     [1 5](max=6,sum=15)

                                                (max=4,sum=8)[1 3]                                         [4 5](max=6,sum=7)

                  (max=3,sum4)[1 2]                  [3 3](max=4,sum=4)               (max=1,sum=1)[4 4] [5 5](max=6,  sum=6)                        ( max=1,sum=1)[1 1] [2 2] (max=3,sum=3)    

 

查询[3 5],就是查询区间内完全包括的叶子节点。

 

每个节点记录的信息

struct Tree
{
    int left,right;//区间端点
    int sum,maxx;//视题目而定
};
如何记录左右儿子?
                    [1 5] 1
             [1 3] 2         [4 5] 3
        [1 2] 4  [3 3] 5    [4 4] 6 [5 5] 7
    [1 1] 8  [2 2] 9 
我们用一个数组a记录节点,且根结点下标为1
对于任一节点Tree[k]
它的左儿子为Tree[2*k](Tree[k<<1]);
它的右儿子为Tree[2*k+1](Tree[k<<1|1])
一维数组即实现了线段树节点信息的保存
建立一颗二叉树,并记录原数组的信息
void build(int id,int l,int r)//节点的编号,此节点代表的区间
{
    tree[id].left=l;tree[id].right=r;
    if(l==r)
    {
        Tree[id].maxx=a[l];
        Tree[id].sum=a[l];
    }
    else
    {
        int mid=(l+r)>>1;
        build(id<<1,l,mid);
        build(id<<1|1,mid+1,r);//递归建树
        Tree[id].maxx=max(Tree[id<<1].maxx,Tree[id<<1|1].maxx);//pushuup的操作,自下而上的操作
        Tree[id].sum=Tree[id<<1].sum+Tree[id<<1|1].sum;//自下而上的操作
    }
}
如果原数组从a[1]~a[n],调用build(1,1,n)即可;
线段树--跟新(跟新某个节点的数值,并维护相关点的信息)
void update(int id,int pos,int val)
{
    if(Tree[id].left==Tree[id].right)//区间端点相等时,也就找到了要修改的数
    {
       Tree[id].sum=val;
       Tree[id].maxx=val;
    }
    else
    {
        int mid=(tree[id].left+tree[id].right)>>1;
        if(pos<=mid) update(id<<1,pos,val);
        else update(id<<1|1,pos,val);
        Tree[id].sum=Tree[id<<1].sum+Tree[id<<1|1].sum;//pushup
        Tree[id].maxx=max(Tree[id<<1].maxx,Tree[id<<1|1].maxx)//pushuup
    }
}
线段树--查询
查询区间内元素的和或最大值
这个区间完全包括的节点
int query(int id,int l,int r)
{
    if(Tree[id].left==l&&Tree[id].right==r)
        return tree[id].sum;
    else
    {
        int mid=(Tree[id].left+Tree[id].right)>>1;
        if(r<=mid)
            return query(id<<1,l,r);
        else if(l>mid)
            return query(id<<1|1,l,r);
        else
            return query(id<<1,l,mid)+query(id<<1|1,mid+1,r)
    }
}
调用query(1,l,r)即可查询[l,r]区间内元素的总和
懒操作在更新区间问题上至关重要,,lazy-tag,pushudown(向下)
线段树之延迟标记(区间修改)
看到这个问题可以立刻想到单点跟新加for循环,很慢,不可取。
延迟标记就是在递归过程中,如果当前区间被需要修改的区间完全覆盖,那么就要停止递归,
并且在上面做一个标记,但是这个信息没有更新到每一个元素(即叶子节点),
这个标记不仅仅是这个节点的性质,此性质作用于该节点的整个子树中,
假设我们另一个查询包含了当前区间的子孙区间,显然这个标记也要对之后的查询产生影响。
单点更新都是在叶节点中实现的,不会对后续节点产生影响,类比这种想法,lazy-tag应运而生
如果当前区间被需要修改的目标区间完全覆盖,打一个标记,如果下一次查询或修改包含此区间
那么,将这个标记分解,并且传递给其左右儿子,此标记去掉。
简单的来说,这个标记,在我们需要时才向下传递信息(pushdown),如果没有用到,则不再进行操作。
为了完成这个操作我们可以在结构体中增加一个add数组存储区间的延迟变化量(修改量)
5 4 3 2 1
[1 3]区间内每个数+2
查询[3 5]区间
                              [1 5](add=0,sum=15)
                (add=0,sum=12)[1 3]                         [4 5](add=0,sum=3)
     (add=0,sum=9[1 2]      [3 3](add=0,sum=3)   (add=0,sum=2)[4 4] [5 5](add=0,sum=1)
(add=0,sum=5[1 1] [2 2](add=0,sum=4)
[1 3]每个数+2
                            [1 5](add=0,sum=15)
                (add=2,sum=12)[1 3]                         [4 5](add=0,sum=3)
     (add=0,sum=9[1 2]      [3 3](add=0,sum=3)   (add=0,sum=2)[4 4] [5 5](add=0,sum=1)
(add=0,sum=5[1 1] [2 2](add=0,sum=4)
 查询[3 5],在向下查找的过程中,发现有一个add值不等于0的点,于是进行pushudown操作下传标记
                                   [1 5](add=0,sum=15)
                (add=0,sum=12)[1 3]                    [4 5](add=0,sum=3)
     (add=2,sum=9[1 2]      [3 3](add=2,sum=3)   (add=0,sum=2)[4 4] [5 5](add=0,sum=1)
(add=0,sum=5[1 1] [2 2](add=0,sum=4)   

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值