线段树总结

时间:2019-09-09
本文章向大家介绍线段树总结,主要包括线段树总结使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

参考题目:A Simple Problem with Integers POJ-3468

距离上一次写这道题已经过去两个月了,前天打模拟赛时连线段树都手敲不出来了。所以这次重新来复习一下线段树。

这次主要是记录一下对线段树区间修改的理解:

一开始我们先是理解线段树的建树原理以及查询原理。利用一个二叉树建立,每个父节点可以记录左右子结点的和或者最大值,以此来维护区间内容。

查询也类似,通过递归寻找,找到所有符合条件(区间内深度最浅)结点,然后将其求和返回或者求最大值返回。

而在之后有单点修改。单点修改重建树来理解,就是递归,找到范围(l,r) ,l=r 时的结点位置更新并 push_up() 向上建树。

但是对于大量数据查询,以及面对区间修改问题时,要一个一个找到位置再更新push_up()就会非常费时间。

所以我们试想能否使用一个标记 tag 传递,在更新范围内的结点就把 tag 传递下去,然后对所有含有tag 的叶子结点更新,再push_up(); 

但是对于上面实际上还有更加优化的方案:即 lazy[] 标记。我们用 lazy[]标记记录每个结点修改信息,就像上面一样。但是当递归到某个完全被包含于查询区间的 部分时,我们直接对这个区间更新 即  (r-l+1) * lazy[p] . (完全被包含,所以直接乘以长度即可),单接下来我就直接返回了,不往下递归了。那没有被更新的那些子结点怎么办?

既然叫 lazy[] 就是真的懒的意思。如果你下次查的区间不包含我要更新的所有叶子结点(或者没有被更新到的子结点部分),那我直接利用已有的信息就可以返回你要的答案。

如果你查的区间包含的话,那我就在查询的同时把那些子结点更新掉,利用我之前 残留下没有更新的 lazy[] 值。所以这样大大减少了修改的时间复杂度。

code:

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <complex>
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define mp make_pair
#define Accept 0
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const double Pi = acos(-1.0);
const double esp = 1e-9;
const int inf = 0x3f3f3f3f;
const int maxn = 5e5+7;
const int maxm = 1e6+7;
const int mod = 1e9+7;

int n,m;
struct segmentTree
{
    ll arr[maxn];
    ll tree[maxn<<2];
    ll lazy[maxn<<2];
    //结点从1开始到n
    void init(int n){
        for(int i=1;i<=n;i++){
            scanf("%lld",&arr[i]);
        }
    }
    void build(int l,int r,int p){
        lazy[p] = 0;
        if(l==r) { tree[p] = arr[l]; return ;}
        int mid = (l+r)>>1;
        build(l,mid,p<<1);
        build(mid+1,r,p<<1|1);
        sum_up(p);
    }
    void sum_down(int p,int m){
        if(lazy[p]){
            lazy[p<<1] += lazy[p];
            lazy[p<<1|1] += lazy[p];
            tree[p<<1] += (ll)lazy[p]*(m-(m>>1));
            tree[p<<1|1] += (ll)lazy[p]*(m>>1);
            lazy[p] = 0;
        }    
    }
    void sum_up(int p){
        tree[p] = tree[p<<1] + tree[p<<1|1];
    }   
    void update(int ql,int qr,int k,int l,int r,int p){
        //完全包含区间,直接返回就不更新其子结点了
        //直到下次需要子结点时才更新 
        if(ql<=l&&r<=qr){
            tree[p] += (ll) (r-l+1) * k;
            lazy[p] +=  k;//用lazy[p]暂时保存所有跟新值(次数) 
            return ;
        }
        sum_down(p,r-l+1);
        int mid = (l+r)>>1;
        //部分包含左区间 
        if(ql<=mid) update(ql,qr,k,l,mid,p<<1);
        //部分包含右区间 
        if(qr>mid) update(ql,qr,k,mid+1,r,p<<1|1);
        sum_up(p);
    }
    ll query(int ql,int qr,int l,int r,int p){
        ll res = 0;
        if(ql<=l&&r<=qr){
            return tree[p];
        }
        sum_down(p,r-l+1);
        int mid = (l+r)>>1;
        if(ql<=mid) res += query(ql,qr,l,mid,p<<1);
        if(qr>mid) res += query(ql,qr,mid+1,r,p<<1|1);
        return res;
    }
}seg;

int main(){
    scanf("%d %d",&n,&m);
    seg.init(n);
    seg.build(1,n,1);
    string s;
    for(int i=0;i<m;i++){
        cin>>s;
        if(s[0]=='Q'){
            int L,R;
            scanf("%d %d",&L,&R);
            printf("%lld\n",seg.query(L,R,1,n,1));
        }else{
            int L,R,k;
            scanf("%d %d %d",&L,&R,&k);
            seg.update(L,R,k,1,n,1);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Tianwell/p/11494670.html