zoukankan      html  css  js  c++  java
  • 线段树【部分转】

    转载声明

    本文为了弥补自己打的代码不知道为什么过不了luogu而作

    只摘录重要的内容(个人认为)

    原出处Senior Data Structure · 浅谈线段树(Segment Tree)

    简介

    ps : _此处以询问区间和为例。实际上线段树可以处理很多符合结合律的操作。(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[2])+(a[3]+a[4]))

    线段树之所以称为“树”,是因为其具有树的结构特性。线段树由于本身是专门用来处理区间问题的(包括 RMQRSQ 问题等。

    思想:

    线段树就是分块思想的树化,或者说是对于信息处理的二进制化

    通过将整个序列分为有穷个小块,对于要查询的一段区间,总是可以整合成 k 个所分块与 m 个单个元素的信息的并 (0<=k,m<=logn)(0<=k,m<=logn)

    建树和维护

      void push_up_sum(int p){
            t[p]=t[lc(p)]+t[rc(p)];
        }// 向上不断维护区间操作 
    
        void push_up_min(int p){//max and min
         t[p]=min(t[lc(p)],t[rc(p)]);
         //t[p]=max(t[lc(p)],t[rc(p)]);             
        }
    

    解释 :

    此处一定要注意, push up操作的目的是为了维护父子节点之间的逻辑关系。当我们递归建树时,对于每一个节点我们都需要遍历一遍,并且电脑中的递归实际意义是先向底层递归,然后从底层向上回溯,所以开始递归之后必然是先去整合子节点的信息,再向它们的祖先回溯整合之后的信息。(这其实是正确性的证明啦)

    呐,我们在这儿就能看出来,实际上 push_up是在合并两个子节点的信息,所以需要信息满足结合律!

    对于建树: 我们应该维护父子节点的关系

    区间修改

    1. 思想详见出处
    2. 懒标记

    首先,懒标记的作用是记录每次、每个节点要更新的值,也就是 delta,但线段树的优点不在于全记录(全记录依然很慢qwq),而在于传递式记录:

    整个区间都被操作,记录在公共祖先节点上;只修改了一部分,那么就记录在这部分的公共祖先上;如果四环以内只修改了自己的话,那就只改变自己。

    如果我们采用上述的优化方式的话,我们就需要在每次区间的查询修改时 pushdown一次,以免重复或者冲突或者爆炸 qwq

    那么对于 pushdown而言,其实就是纯粹的 push up的逆向思维(但不是逆向操作): 因为修改信息存在父节点上,所以要由父节点向下传导 lazy tag

    那么问题来了:怎么传导 pushdown呢?这里很有意思,开始回溯时执行 push up ,因为是向上传导信息;那我们如果要让它向下更新,就调整顺序,在向下递归的时候 pushdown不就好惹~ qwq:

    注: ans[]为需要维护的

    inline void f(ll p,ll l,ll r,ll k)
    {
        tag[p]=tag[p]+k;
        ans[p]=ans[p]+k*(r-l+1);
        //由于是这个区间统一改变,所以ans数组要加元素个数次啦 
    }
    //我们可以认识到,f函数的唯一目的,就是记录当前节点所代表的区间 
    inline void push_down(ll p,ll l,ll r)
    {
        ll mid=(l+r)>>1;
        f(ls(p),l,mid,tag[p]);
        f(rs(p),mid+1,r,tag[p]);
        tag[p]=0;
        //每次更新两个儿子节点。以此不断向下传递 
    }
    inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
    {
        //nl,nr为要修改的区间
        //l,r,p为当前节点所存储的区间以及节点的编号 
        if(nl<=l&&r<=nr)
        {
            ans[p]+=k*(r-l+1);
            tag[p]+=k;
            return ;
        }
        push_down(p,l,r);
        //回溯之前(也可以说是下一次递归之前,因为没有递归就没有回溯) 
        //由于是在回溯之前不断向下传递,所以自然每个节点都可以更新到 
        ll mid=(l+r)>>1;
        if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
        if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
        push_up(p);
        //回溯之后 
    }
    

    查询区间

    ll query(ll q_x,ll q_y,ll l,ll r,ll p)
    {
        ll res=0;
        if(q_x<=l&&r<=q_y)return ans[p];
        ll mid=(l+r)>>1;
        push_down(p,l,r);
        if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
        if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
        return res;
    }
    

    标程

    #include<iostream>
    #include<cstdio>
    #define MAXN 1000001
    #define ll long long
    using namespace std;
    unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
    inline ll ls(ll x)
    {
        return x<<1;
    }
    inline ll rs(ll x)
    {
        return x<<1|1;
    }
    void scan()
    {
        cin>>n>>m;
        for(ll i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    }
    inline void push_up(ll p)
    {
        ans[p]=ans[ls(p)]+ans[rs(p)];
    }
    void build(ll p,ll l,ll r)
    {
        tag[p]=0;
        if(l==r){ans[p]=a[l];return ;}
        ll mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        push_up(p);
    } 
    inline void f(ll p,ll l,ll r,ll k)
    {
        tag[p]=tag[p]+k;
        ans[p]=ans[p]+k*(r-l+1);
    }
    inline void push_down(ll p,ll l,ll r)
    {
        ll mid=(l+r)>>1;
        f(ls(p),l,mid,tag[p]);
        f(rs(p),mid+1,r,tag[p]);
        tag[p]=0;
    }
    inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
    {
        if(nl<=l&&r<=nr)
        {
            ans[p]+=k*(r-l+1);
            tag[p]+=k;
            return ;
        }
        push_down(p,l,r);
        ll mid=(l+r)>>1;
        if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
        if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
        push_up(p);
    }
    ll query(ll q_x,ll q_y,ll l,ll r,ll p)
    {
        ll res=0;
        if(q_x<=l&&r<=q_y)return ans[p];
        ll mid=(l+r)>>1;
        push_down(p,l,r);
        if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
        if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
        return res;
    }
    int main()
    {
        ll a1,b,c,d,e,f;
        scan();
        build(1,1,n);
        while(m--)
        {
            scanf("%lld",&a1);
            switch(a1)
            {
                case 1:{
                    scanf("%lld%lld%lld",&b,&c,&d);
                    update(b,c,1,n,1,d);
                    break;
                }
                case 2:{
                    scanf("%lld%lld",&e,&f);
                    printf("%lld
    ",query(e,f,1,n,1));
                    break;
                }
            }
        }
        return 0;
    }
    

    以下为luogu听课时的代码

    如果看懂了上面的,就完全不用看下面的了,博主只是觉得下面的码风比较亲民...

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e6+10;
    int n,a[N],m;
    int sumv[N<<2],addv[N<<2];
    int pushup(int o){sumv[o]=sumv[o<<1]+sumv[o<<1|1];}
    void build(int o,int l,int r){
    	addv[o]=0;//注意初始化
    	if(l==r){sumv[o]=a[l];return;}
    	int mid=(l+r)>>1;
    	build(o<<1,l,mid);
    	build(o<<1|1,mid+1,r);
    	pushup(o);
    }
    inline void puttag(int o,int l,int r,int v){//puttag的意思是到了该修改信息的结点
    	addv[o]+=v;sumv[o]+=(r-l+1)*v;//先记录一下tag,然后算出本节点增加的sum(注;增加的是v,这里的addv只是标记
    }
    void pushdown(int o,int l,int r){
    	if(addv[o]==0)return;//注意addv可能为0
    	addv[o<<1]+=addv[o];
    	addv[o<<1|1]+=addv[o];
    	int mid=(l+r)>>1;
    	sumv[o<<1]+=addv[o]*(mid-l+1);//注意这是 ..'+'= addv['o']...
    	sumv[o<<1|1]+=addv[o]*(r-mid);//r-(mid+1)+1
    	addv[o]=0;
    }
    void optadd(int o,int l,int r,int ql,int qr,int v){
    	if(ql<=l&&r<=qr){puttag(o,l,r,v);return;}//到了结点
    	int mid=(l+r)>>1;
    	pushdown(o,l,r);
    	if(ql<=mid)optadd(o<<1,l,mid,ql,qr,v);
    	if(qr>mid)optadd(o<<1|1,mid+1,r,ql,qr,v);
    	pushup(o);
    }
    int querysum(int o,int l,int r,int ql,int qr){
    	if(ql<=l&&r<=qr)return sumv[o];//到了结点
    	int ans=0;int mid=(l+r)>>1;
    	pushdown(o,l,r);
    	if(ql<=mid)ans+=querysum(o<<1,l,mid,ql,qr);
    	if(qr>mid)ans+=querysum(o<<1|1,mid+1,r,ql,qr);//注:是mid “<” qr
    	return ans;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%d",a+i);
    	build(1,1,n);
    	while(m--){
    		int opt;scanf("%d",&opt);
    		if(opt==1){int l,r,v;scanf("%d%d%d",&l,&r,&v);optadd(1,1,n,l,r,v);}
    		if(opt==2){int l,r;scanf("%d%d",&l,&r);printf("%d
    ",querysum(1,1,n,l,r));}
    	}
    }
    
    

    几句有用的话:

    需要修改某点,但是却不访问它,这样就没必要执行修改操作,所以线段树的区间修改有了lazytag

    修改了就需要push_up

    记得addv和sumv一起改

    不要老是想着非叶子结点的lazytag,它会在修改和访问的时候下传,而在下传之前pushtag操作和pushdown操作就把sumv等附加信息更新好了(即下传的时候更新),所以在query的时候直接返回了sumv,所以记得addv和sumv一起改

    修改和查询时没有完全覆盖的话,都需要push_down(其实这个理解了上面的原理就都懂了,不懂的看下面...(原谅语文不好...0.0
    (修改时没有全覆盖的话,就需要下传addv了,这时用子节点的信息更新本节点的信息,然后这时候就不用操心本节点的addv啦(如果覆盖了就直接修改嘛,注意:一定要更新本节点的信息,不然在本节点的信息就是错的,万一在查询的时候刚好覆盖了本节点,那么返回的就是个错误的信息
    (查询没有被完全覆盖的区间时,只有先push_down, 算出子节点的信息了,才能算本节点在[ql,qr]范围内的附加信息(如果覆盖了就直接返回嘛,原因如上

  • 相关阅读:
    aul 学习测试(测量)
    ubuntu12.04下一个samba、tftp、nfs构造
    Linux互斥和同步应用程序(一):posix线程和线程之间的相互排斥
    Cocos2d-x加速度计
    事务处理和并发控制
    【通过做专题研习Android】知识点:SharedPreferences
    数据结构 --- 单人名单
    安德鲁斯 建立与各种听众自己定义的ScrollView
    【iOS开展-94】xcode6如何使用GIT以及如何添加太老项目GIT特征?
    2013年第35周一
  • 原文地址:https://www.cnblogs.com/tyner/p/11041161.html
Copyright © 2011-2022 走看看