线段树

1.线段树是什么?

基于分治思想的二叉树结构,用于区间操作。

2.主要操作(例子是:线段树维护区间最大值)

  • 建树

  一点点分着向下拓展,左儿子编号是父节点编号*2,右儿子编号是左儿子编号+1;

void create(int fa,int l,int r,long long t[])
{
    stree[fa].lazy=0;
    if(l==r)
        stree[fa].val=t[l];
    else
    {
        int mid=(l+r)/2;
        create(2*fa,l,mid,t);
        create(2*fa+1,mid+1,r,t);
        stree[fa].val=stree[2*fa].val+stree[2*fa+1].val;
    }
}
  • 单点修改

  像建树时的一点点拓展一样,我们需要一直二分找到该点,并且把这个点父节点和祖父节点的所有区间都更新 

void change (int fa,int x,int v)
{
     if(stree[fa].l==stree[fa].r)
     {
            stree[fa].val=v;
            return;
     }   
      int mid=(stree[fa].l+stree[fa].r)>>1;
      if(x<=mid) change(fa*2,x,v);
      else change(fa*2+1,x,v);
      stree[fa].val=max(stree[fa*2].val,stree[fa*2+1].val);
    
}            
  • 区间查询

  查询 区间【l,r】的最大值;

  1.若【l,r】完全覆盖了当前节点代表的区间,立即回溯,并且该节点的val为候选

  2.若左端点与【l,r】有重叠部分,就递归进入左儿子

  3.同理,若右端点与【l,r】有重叠部分,就递归进入右儿子

int askaskask(int fa,int l,int r)
{
    if(l<=stree[fa].l&&r>=stree[fa].r)
        return stree[fa].val;
    int mid=(stree[fa].l+stree[fa].r)>>1;
    int ans=-0x3f3f3f3f
    if(l<=mid)
        ans=max(ans,askaskask(fa*2,l,r));
    else 
        ans=max(ans,askaskask(fa*2+1,l,r));
    return ans;   
}
  • 区间加和区间乘(需要延迟标记和下放操作)

  如果我们进行区间加的时候,区间内每一个都加,还是要递归进行,每个子节点都会被更新到,那么复杂度就从o(logn)上升到o(n)了,我们肯定不想这种情况出现,所以我们有了

延迟标记(只在要修改的区间标记上我们干啥了,先不像单点修改那样更新父节点),顾名思义,延迟标记的作用是让我们只有在用的时候才把它下放,标记的时候是o(1),下放的时候是o(logn)的,保证了插入的时间复杂度;

  那么问题来了,我们怎么下放???

  直接看代码,简单的只有加和只有乘你们通过这个既有加又有乘的例子自行体会。

void shutdown(int root,int l,int r)//下放
{
    if(point[root].mull!=1)//先下放乘法标记,再下放加法标记;
    {
        point[2*root].addl=(point[root].mull*point[2*root].addl)%p;
        point[2*root].mull=(point[root].mull*point[2*root].mull)%p;
        point[2*root+1].addl=(point[root].mull*point[2*root+1].addl)%p;
        point[2*root+1].mull=(point[root].mull*point[2*root+1].mull)%p;
        point[2*root].value=(point[2*root].value*point[root].mull)%p;
        point[2*root+1].value=(point[2*root+1].value*point[root].mull)%p;
        point[root].mull=1; //下放完记得清空标记;
    }
        int mid=(l+r)>>1;
        point[2*root].addl+=point[root].addl;
        point[2*root].value+=point[root].addl*(mid-l+1);
        point[2*root+1].addl+=point[root].addl;
        point[2*root+1].value+=point[root].addl*(r-mid);
        point[root].addl=0;
}
void add(int root,int l,int r,int ll,int rr,int plus)//区间加
{
    if(l>rr||r<ll)
        return ;
    else if(l>=ll&&r<=rr)
    {
        point[root].addl+=plus;
        point[root].value+=plus*(r-l+1);
        return ;
    }
    else
    {
        int mid=(l+r)>>1;
        shutdown(root,l,r);
        add(2*root,l,mid,ll,rr,plus);
        add(2*root+1,mid+1,r,ll,rr,plus);
        point[root].value=point[2*root].value+point[2*root+1].value;
    }
}
void mul(int root,int l,int r,int ll,int rr,int multiply)//区间乘
{
    if(l>rr||r<ll)
        return ;
    else if(l>=ll&&r<=rr)
    {
        point[root].addl=(point[root].addl*multiply)%p;
        point[root].mull=(point[root].mull*multiply)%p;
        point[root].value=(point[root].value*multiply)%p;
        return ;
    }
    else
    {
        int mid=(l+r)>>1;
        shutdown(root,l,r);
        mul(2*root,l,mid,ll,rr,multiply);
        mul(2*root+1,mid+1,r,ll,rr,multiply);
        point[root].value=point[2*root].value+point[2*root+1].value;
    }
}

3.注意事项

如果是一个有N个叶子节点的二叉树,共有2n-1个节点,又因为我们用父子二倍的节点编号,所以保存线段树的数组长度不小于4N才能保证不越界;

 

 

 

 

可以做两道模板题玩一玩:洛谷p3372 p3373

还有一道我觉得很神,巨佬们觉得很水的题:洛谷p4198 楼房重建。(其实考的就是思路)

 

posted @ 2018-07-03 16:54  S-Royal  阅读(210)  评论(0编辑  收藏  举报