zoukankan      html  css  js  c++  java
  • 线段树小记

    日常废话:

    不知道为什么对water_lift的线段树板子有着迷之执着,就是想用water_lift的板子。(但是忘记water_lift怎么写的了然后又找不到类似的板子只能与一本通为伍了qwq)


    线段树?是什么:

    线段树是一种数据结构,用来维护一段区间的某个特征(比如最小值、和、积……)的二叉树。最简单的应用大概就是求和、维护最小值一类,当然线段树复杂起来让你想象不到。

    线段树的几个基本操作:

    以求区间和的区间查询,单点修改,区间修改为eg

    首先的说,其实本来想先写一个朴素的会炸的区间修改的,结果我不会emm,所以直接加lazy_tag了。

    Lazy_tag:

    延迟标记,如果区间修改时我们递归到叶节点在进行修改的话,复杂度是O(mlogn),比朴素还要慢,因此有延迟下传的标记,当我们暂时用不到此子树时,不需进行更新,只需要在树根上打一个标记,然后维护此根节点的值。当用到此子树时,再进行下传。

    假装所有的基本操作都已经写好了:

    #include<bits/stdc++.h>
    #define ll long long
    
    using namespace std;
    inline ll read(){
        ll ans=0;
        char last=' ',ch=getchar();
        while(ch>'9'||ch<'0') last=ch,ch=getchar();
        while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
        if(last=='-') ans=-ans;
        return ans;
    }
    const ll N=100005;
    
    ll n,m;
    ll val[N];
    ll sum[N*4],laz[N*4];
    
    int main(){
        n=read();m=read();
        for(int i=1;i<=n;i++)
          val[i]=read();
        build(1,1,n);
        ll ops,x,y,k;
        for(int i=1;i<=m;i++){
            ops=read();x=read();y=read();
            if(ops==1){
                k=read();
                Add(1,1,n,x,y,k);
            }
            else printf("%lld
    ",query(1,1,n,x,y));
        }
        return 0;
    }

    1.建树;

    “你在写什么啊?”

    “线段树。”

    “树呢?”

    没有树的线段树是没有灵魂的,因此第一步显然是建树;(大概我只会写建树了

    建树代码:

    void build(ll node,ll x,ll y){
        if(x==y){
            sum[node]=val[x];
            return;
        }
        ll mid=x+y>>1;
        build(node<<1,x,mid);
        build(node<<1|1,mid+1,y);
        sum[node]=sum[node<<1]+sum[node<<1|1];
    }

    解释一下:

    ll node表示当前要建的结点编号,ll x表示此区间的左端点,ll y表示此区间的右端点;

    当x==y时,也就是叶子结点,非常容易就可以知道此结点的sum就是它本身的值。

    然后因为线段树是非常严格的二叉树,因此它的每个儿子的编号都是确定的,分别为node*2和node*2+1;因此递归的建树,最后维护区间和就好了;

    2.区间查询操作:

    先上代码:

    ll query(ll node,ll l,ll r,ll x,ll y){
        if(l>=x&&r<=y)//递归到的区间完全被包含在要查询区间中
          return sum[node];
        ll mid=l+r>>1;
        ll res=0;
    pushdown(node,l,r,mid);
    if(x<=mid) res+=query(node<<1,l,mid,x,y); if(mid<y) res+=query(node<<1|1,mid+1,r,x,y); return res; }

    解释一波:

    因为查询时一定是从1~n开始查询,然后递归查询。所以当递归到一个完全被包含在要查询区间的数,显然可以不用向下继续递归了,因为这个点已经维护了这段区间的和,直接返回就可以;然后判断区间中点,显然如果出现:

    x<=mid的情况,也就表示我们可以在node 的左儿子中查询到一个区间(区间的大小我们无法确定,可能是整个左儿子,也可能是左儿子的一个叶节点),那么就需要去递归的求左儿子

     y>mid的情况,也就说明在右儿子中一定包含一部分是在区间[x,y]中,所以需要递归右儿子;

     然后将左儿子和右儿子递归到的值加起来,就是我们要查询的区间的和;

    3.单点修改(不会写需现学)

    CODE:

    void change(ll node,ll l,ll r,ll x,ll v){//单点加v 
        if(r<x||l>x) return;//当右区间比要修改的x的值要小时,显然递归右区间没有用
        //左区间同理
        if(l==r&&l==x){
            sum[node]+=v;
            return; 
        } 
        ll mid=l+r>>1;
        change(node<<1|1,mid+1,r,x,v);
        change(node<<1,l,mid,x,v);
        sum[node]=sum[node<<1]+sum[node<<1|1]; 
    }

    解释一波~

    首先判断不可能有解的情况:当前递归到的区间的最右要比x小时

    显然再递归下去也不可能找到解,所以直接return好啦;

    同样当递归到的区间的最左比x大时,显然也不行:

    因此这两种情况都直接return;

    当递归到了我们需要修改的x时:修改对应sum值,返回。

    然后对于不是需要修改的x但是将x包含在内的区间,我们分别递归的求它的左右儿子,最后还有重新维护sum的值;

    4.区间修改:

    先上代码++:

    void add(ll node,ll l,ll r,ll k){
        laz[node]+=k;
        sum[node]+=(r-l+1)*k; 
    }
    void pushdown(ll node,ll l,ll r,ll mid){
        if(laz[node]==0) return;
        add(node<<1,l,mid,laz[node]);
        add(node<<1|1,mid+1,r,laz[node]);
        laz[node]=0;
    }
    
    void Add(ll node,ll l,ll r,ll x,ll y,ll k){
        if(x<=l&&y>=r) return add(node,l,r,k);
        ll mid=l+r>>1;
        pushdown(node,l,r,mid);
        if(x<=mid) Add(node<<1,l,mid,x,y,k);
        if(y>mid) Add(node<<1|1,mid+1,r,x,y,k);
        sum[node]=sum[node<<1]+sum[node<<1|1];
    } 

    开始解释:

    首先判断,如果当前的递归区间完全包含在区间[x,y]中,直接对这个区间进行add操作,维护这个区间的区间和,然后打lazy_tag;

    然后还是相应的分别递归左儿子和右儿子,还是相应的判断是否需要递归。最后不要忘记维护区间的和。

    然后确实没有搞明白为啥此时要pushdown。

     pushdown就是标记下传的过程;


    CODE:

    #include<bits/stdc++.h>
    #define ll long long
    const int N=100005;
    
    using namespace std;
    
    inline ll read(){
        int ans=0;
        char last=' ',ch=getchar();
        while(ch>'9'||ch<'0') last=ch,ch=getchar();
        while(ch<='9'&&ch>='0') ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
        if(last=='-') ans=-ans;
        return ans;
    }
    
    ll n,m;
    ll val[N],sum[N*4],laz[N*4];
    
    void build(ll node,ll x,ll y){//建树
        if(x==y){
            sum[node]=val[x];
            return;
        }
        ll mid=x+y>>1;
        build(node<<1,x,mid);
        build(node<<1|1,mid+1,y);
        sum[node]=sum[node<<1]+sum[node<<1|1];
    }
    
    void add(ll node,ll l,ll r,ll k){//添加维护 
        laz[node]+=k;
        sum[node]+=(r-l+1)*k; 
    }
    void pushdown(ll node,ll l,ll r,ll mid){//标记下传 
        if(laz[node]==0) return;
        add(node<<1,l,mid,laz[node]);
        add(node<<1|1,mid+1,r,laz[node]);
        laz[node]=0;
    }
    
    ll query(ll node,ll l,ll r,ll x,ll y){//查询 
        if(l>=x&&r<=y)//递归到的区间完全被包含在要查询区间中
          return sum[node];
        ll mid=l+r>>1;
        ll res=0;
        pushdown(node,l,r,mid); 
        if(x<=mid) res+=query(node<<1,l,mid,x,y);
        if(mid<y) res+=query(node<<1|1,mid+1,r,x,y);
        return res; 
    }
    
    void change(ll node,ll l,ll r,ll x,ll v){//单点加v 
        if(r<x||l>x) return;//当右区间比要修改的x的值要小时,显然递归右区间没有用
        //左区间同理
        if(l==r&&l==x){
            sum[node]+=v;
            return; 
        } 
        ll mid=l+r>>1;
        change(node<<1|1,mid+1,r,x,v);
        change(node<<1,l,mid,x,v);
        sum[node]=sum[node<<1]+sum[node<<1|1]; 
    }
    
    void Add(ll node,ll l,ll r,ll x,ll y,ll k){//区间加k 
        if(x<=l&&y>=r) return add(node,l,r,k);
        ll mid=l+r>>1;
        pushdown(node,l,r,mid);
        if(x<=mid) Add(node<<1,l,mid,x,y,k);
        if(y>mid) Add(node<<1|1,mid+1,r,x,y,k);
        sum[node]=sum[node<<1]+sum[node<<1|1];
    } 
    
    int main(){
        n=read();m=read();
        for(ll i=1;i<=n;i++) val[i]=read();
        build(1,1,n);
        int ops;
        ll x,y,k;
        for(ll i=1;i<=m;i++){
            ops=read(); x=read();y=read();
            if(ops==1){
                k=read();
                Add(1,1,n,x,y,k);
            }
            else printf("%lld",query(1,1,n,x,y));
        }
        return 0;
    }

    对了注意开空间要开序列长度N的4倍,为啥我也不是很清楚,反正不能开二倍。

    好了没有了

    end-

  • 相关阅读:
    Linux 常用命令 2
    Linux常用的命令
    linux的发行版
    操作系统介绍
    Python学习-列表的转换和增加操作
    Python学习-列表的修改,删除操作
    Python学习-初始列表
    Python学习-range的用法
    Python学习-字符串的基本知识
    Python学习-字符串函数操作3
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11107100.html
Copyright © 2011-2022 走看看