zoukankan      html  css  js  c++  java
  • 线段树(模板)

     

       线段树它是一棵高度平衡的二叉树,很多二叉树的性质它是完美继承的

    我们来看一道题:

    HDU1166敌兵布阵

    这道题如果用常规暴力的做法,就把所有营地的士兵存在一个数组里面,然后对于每次操作直接更新对应位置的数,对于每次询问直接从i到j加起来。然而这么操作下来,对于极限数据50000个人,40000条命令,显然是会超时的,那么一种新的数据结构线段树就应运而生了。

    首先第一个疑问:为什么线段树会快?

    显然对于m个点n次询问,暴力的做法时间复杂度是O(m*n)的。然而线段树作为一棵二叉树,继承了二叉树O(logn)的优良品质,对于这道题最坏的复杂度也是O(m*logn)的,这个量显然是符合时间要求的。

    第二:线段树如何处理?

    倘若节点x(x为奇数)记录的是第1个点的数据,节点x+1记录的是第2个点的数据,那么节点x/2记录的就是区间[1,2]上的有效数据,以此类推,最顶端的父节点记录的就是区间[1,n]上的有效数据,那么对于每个点的数据,有且仅有logn个节点的数据会被它影响,因此每次更新只用更新logn个点,查询亦然,这样就有效地节约了时间。

    对于每个节点,其代表的是区间[x,y]之间的值,那么其左儿子节点代表的就是[x,(x+y)/2]区间的值,右儿子节点代表的是区间[(x+y)/2+1,y]上的值,既保证了无重复,又保证了树的层数最短,查询效率最高。

    第三:线段树的具体实现呢?

    那么我们就跟着刚才拿到题目来详细讲解。

    首先是建树,在这里num存的是下标,而le和ri表示的是这个区间的左右端点,那么每往下一层num*2,区间则折半,保证了最少的层数,而此时内存占用大约为4倍的点数,所以开数组的时候开tre[4*N]。这个题因为需要读入每个点,作为二叉树的先序遍历,很好地保证了第x个点正好读入在le=ri=x的那个tre[num]里面。而父亲节点所代表的区间包含了子节点所代表的区间,所以子节点的值又会影响父节点,因此每次建立完儿子节点之后,又会通过tre[num]=tre[num*2]+tre[num*2+1];操作将父亲节点初始化,当然此处为求和操作所以是+,不同的题可以选择取最值等不同运算符。当然不同的题根据需求可以采取对tre[num]赋值或者memset等方法来建树以及初始化。

    int tre[N*4];
    void build(int num,int le,int ri)//num是线段树下标,le,ri是区间的左右端点
    {
        if(le==ri)
        {
            scanf("%d",&tre[num]);
            return ;
        }
        int mid=(le+ri)/2;
        build(num*2,le,mid);
        build(num*2+1,mid+1,ri);
        tre[num]=tre[num*2]+tre[num*2+1];//通过子节点初始化父亲节点
    }

      接下来是修改操作,继承了上面的num,le,ri,保证了一致性,同时此处做的是对于第x个点增加y个人的操作,所以寻找到x所对应的tre[num],然后操作,并回退。而此时需要注意的是,对于x操作了之后,所有包含x的区间的tre[num]都需要被修改,因此也就有了在回退前的tre[num]=tre[num*2]+tre[num*2+1];操作。而这个题操作的是增加减少(减少直接传-x),而其他的诸如取最大最小值、取异或值等等都只用对于对应的运算符做修改即可。

      区间更新与单点更新最大的不同就是:它多了一个lazy数组!!!!!!!!!!重要的地方要打10个感叹号。

    laz,全称lazy,中文叫懒惰标记或者延迟更新标记。

    因为我们知道,如果我们每次都把段更新到节点上,那么操作次数和每次对区间里面的每个点单点更新是完全一样的哇!那么怎么办呢?仔细观察线段树,你会发现一个非常神奇的地方:每个节点表示的值都是区间[le,ri]之间的值有木有!!!!!!!!!!为什么说它神奇呢?更新的区间的值,存的区间的值!简直就是天作之合,我每次更新到对应区间了我就放着,我等下次需要往下更新更小的区间的时候,再把两次的值一起更新下去有木有啊!可以节约非常多时间啊有木有啊!

    对,这就是laz[num]的作用。下面我们跟着题再来逐步感受。

    首先在最最最最最最开始,是没有进行过更新操作的,那么laz[num]自然是全部置为0(当然有的题有额外的初始化要求,大家根据题目自行定夺)。

    那么初始化结束之后,就开始更新操作。

    一、单点更新

    void update(int num,int le,int ri,int x,int y)//num是线段树下标,le,ri是区间范围,将位置x的值更新为y
    {
        if(le==ri)
        {
            tre[num]+=y;
            return ;
        }
        int mid=(le+ri)/2;
        if(x<=mid)
            update(num*2,le,mid,x,y);
        else
            update(num*2+1,mid+1,ri,x,y);
        tre[num]=tre[num*2]+tre[num*2+1];
    }

    二、区间更新

    void update(int num,int le,int ri,int x,int y)//更新区间[x,y]上的值,[le,ri]是当前正在更新的子区间
    {
        if(x<=le&&y>=ri)
        {
            tre[num]++;
            laz[num]++;
            return ;
        }
        pushdown(num);
        int mid=(le+ri)/2;
        if(x<=mid)
            update(num*2,le,mid,x,y);
        if(y>mid)
            update(num*2+1,mid+1,ri,x,y);
    }

    三、懒人标记

    void pushdown(int num)
    {
        if(laz[num]!=0)
        {
            tre[num*2]+=laz[num];
            tre[num*2+1]+=laz[num];
            laz[num*2]+=laz[num];
            laz[num*2+1]+=laz[num];
            laz[num]=0;
        }
    }

      最后是查询操作,依然继承了num,le,ri。而此处做的是区间查询,(其实如果x=y就成了单点查询)那么如果查询区间[x,y]包含了目前的区间[le,ri],即x<=le&&y>=ri,那么此时的tre[num]就已经是这一部分的有效数据了,所以直接return即可,否则继续分区间查询。同样,此时根据题意所做的求和操作可以对应替换为异或、取最值等操作。

    int query(int num,int le,int ri,int x,int y)
    {
        if(x<=le&&y>=ri)
        {
            return tre[num];
        }
        int mid=(le+ri)/2;
        int ans=0;
        if(x<=mid)
            ans+=query(num*2,le,mid,x,y);
        if(y>mid)
            ans+=query(num*2+1,mid+1,ri,x,y);
        return ans;
    }

    以上转载自:https://blog.csdn.net/zip_fan/article/details/46775633

  • 相关阅读:
    COGS2355 【HZOI2015】 有标号的DAG计数 II
    COGS2353 【HZOI2015】有标号的DAG计数 I
    COGS2259 异化多肽
    二项式定理
    Codeforces 528D Fuzzy Search
    技术文章阅读-华为WS331a产品管理页面存在CSRF漏洞
    技术文章阅读-天翼创维awifi路由器存在多处未授权访问漏洞
    技术文章阅读-红蓝对抗系列之浅谈蓝队反制红队的手法一二
    技术文章阅读-Solr ReplicationHandler漏洞浅析
    技术文章阅读-记一次edu站点从敏感信息泄露到getshell
  • 原文地址:https://www.cnblogs.com/-citywall123/p/11260985.html
Copyright © 2011-2022 走看看