线段树个人理解及模板

Posted pokimonmaster

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树个人理解及模板相关的知识,希望对你有一定的参考价值。

一.线段树的相关定义及用途

 (1)线段树的定义

    线段树是一种可以加快对区间进行更新以及查询的一种树状结构,类似于将一个区间的及其子区间的相关信息存储在一颗二叉树中。

    (2)线段树的用途大致为以下几种

    1>某个子区间进行区间更新

    2>查询某个子区间的总和

    3>查询某个子区间的最值

二.线段树的建立

  (1)节点的内容(一个节点代表一个区间)

      1>NodeLeft-----该区间的左边界

      2>NodeRight-----该区间的右边界

      3>NodeMin--------该区间的最小值

      4>NodeMax------该区间的最大值

      5>NodeSum------该区间的总和

  (2)利用节点之间的下标表示它们所代表的区间之间的关系

         1>设父节点的编号为n,则其左半区间的编号为2*n,右半区间的编号为2*n+1

      2>设某个非总区间的编号为n,则其父区间编号为n/2

     技术图片

 

三.线段树的实例使用及代码

  (1)实例背景

              技术图片

    (2)建树

      1>将7个红包建立为一颗线段树

    技术图片

      2>建树代码

struct Tree {//定义结构
        ll l,r;//节点左右端点
        ll sum;//求和 
        ll lazy;//延迟标记
        ll maxn;//最大值 
        ll minn;// 最小值 
} t[MAXN<<2];//开4倍空间


void push_up(ll rt) { //向上更新
    t[rt].sum = t[rt << 1].sum + t[rt << 1 | 1].sum;//更新和
    t[rt].maxn = max(t[rt << 1].maxn ,t[rt << 1 | 1].maxn);//更新最大值
    t[rt].minn = min(t[rt << 1].minn ,t[rt << 1 | 1].minn);//更新最小值
}


void build(ll l,ll r, ll rt) { //建树,rt==root 
    t[rt].lazy = 0;
    t[rt].l=l;
    t[rt].r=r;
    if(l == r) {
        scanf("%lld",&t[rt].sum);//单个红包时输入金额
        t[rt].minn=t[rt].sum;
        t[rt].maxn=t[rt].sum;
        return;
    }
    ll mid = (l + r) >> 1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    push_up(rt);//向上更新

}

  (3)得到n~m的红包的金钱总额

 1 #define ll long long
 2 #define lson  rt << 1
 3 #define rson  rt << 1 | 1
 4 
 5 void push_down(ll rt, ll m) {//pushdown函数
 6     if(t[rt].lazy) { //若有标记,则将标记向下移动一层
 7         t[rt << 1].lazy += t[rt].lazy;
 8         t[rt << 1 | 1].lazy += t[rt].lazy;
 9         t[rt << 1].sum += (m - (m >> 1)) * t[rt].lazy;
10         t[rt << 1 | 1].sum += (m >> 1) * t[rt].lazy;
11         t[rt << 1].minn += t[rt].lazy;
12         t[rt << 1 | 1].minn+= t[rt].lazy;
13         t[rt << 1].maxn += t[rt].lazy;
14         t[rt << 1 | 1].maxn+= t[rt].lazy;
15         t[rt].lazy = 0;//取消本层标记
16     }
17 }
18 
19 ll query(ll L, ll R, ll rt) { //区间求和
20     if(L <= t[rt].l && R >= t[rt].r) {//如果当前节点所代表的区间包含于所求区间
21         return t[rt].sum;//直接返回sum
22     }
23     push_down(rt, t[rt].r - t[rt].l + 1);//向下更新,对lazy标记进行处理
24     ll mid = (t[rt].r + t[rt].l) >> 1;
25     ll ans = 0;
26     if(L <= mid) ans += query(L, R, lson);//分两边递归
27     if(R > mid) ans += query(L, R, rson);
28     return ans;
29 }

  (4)更新n~m的金钱数

void update(ll L,ll R, ll key, ll rt) { //区间更新
    if(L <= t[rt].l && R >= t[rt].r) {
        t[rt].sum+=(t[rt].r - t[rt].l + 1) * key;
        t[rt].minn+=key;
        t[rt].maxn+=key;
        t[rt].lazy+=key;
        return;
    }
    push_down(rt, t[rt].r - t[rt].l + 1);//向下更新
    ll mid = (t[rt].r + t[rt].l) >> 1;
    if(L <= mid) update(L, R, key, lson);
    if(R > mid) update(L, R, key, rson);
    push_up(rt);//向上更新
}

  (5)寻找最值

ll query_min(ll L, ll R, ll rt) { //区间求最小值
    if(L <= t[rt].l && R >= t[rt].r) {
        return t[rt].minn;
    }
    push_down(rt, t[rt].r - t[rt].l + 1);//向下更新
    ll mid = (t[rt].r + t[rt].l) >> 1;
    ll ans = 0x3f3f3f3f;
    if(L <= mid) ans = min(ans,query_min(L, R, lson));
    if(R > mid) ans =min(ans,query_min(L, R, rson)) ;
    return ans;
}

ll query_max(ll L, ll R, ll rt) { //区间求最大值
    if(L <= t[rt].l && R >= t[rt].r) {
        return t[rt].maxn;
    }
    push_down(rt, t[rt].r - t[rt].l + 1);//向下更新
    ll mid = (t[rt].r + t[rt].l) >> 1;
    ll ans = 0;
    if(L <= mid) ans = max(ans,query_max(L, R, lson));
    if(R > mid) ans = max(ans,query_max(L, R, rson));
    return ans;
}

以上是关于线段树个人理解及模板的主要内容,如果未能解决你的问题,请参考以下文章

模板线段树-单点修改,区间查询

线段树模板 (poj 3468)

线段树模板总结

树状数组求逆序数及变形(个人理解)

P3373 模板线段树 2

线段树模板 CDOJ1057