线段树(简单版)

使用线段树维护一段长度为\(n\)的区间,线段树的空间要开\(4n\)的原因

设要维护的区间为\([1, n]\)长度为\(n\)

\(n = 2^k, k = 0, 1, 2, 3, ...\)

那么构造的线段树的结点个数不多不少刚好用\(2^{k + 1} - 1 < 2n\)个, 所以开\(2n\)足够,此时的线段树为深度为\(k + 1\)满二叉树。

\(n \neq 2^k\)那么构造这个线段树所用的结点数(包括浪费的)和一个构造区间长度为\(2^t\)的区间的线段树所用的结点数相同其中\(2^t\)为大于n的最小的\(2\)的次幂。

下面来求 \(t\)

因为\(n < 2^t\)

那么\(log_{2}n < t\)

所以\(t\)是大于\(log_{2}n\)的最小整数

所以\(t = \lfloor log_{2}n \rfloor + 1\)

由于构造长度为\(2^t\)的区间的线段树需要\(2^{t + 1} - 1\)个结点,即\(2^{ \lfloor log_{2}n \rfloor + 1 + 1} - 1\)个结点, 即\(4 * 2^{ \lfloor log_{2}n \rfloor} - 1 \lt 4n\),所以为了保证不越界,开\(4n\)空间。

通过浪费一些空间,来让线段树具有完全二叉树的性质(左孩子,右孩子,双亲)\(n \neq 2^k\)时,画出线段树,可以明显看出它不是完全二叉树。

线段树的层数问题:

\(n = 2^k\),最后一层结点数为\(n\),层数为\(log_{2}n + 1\)

\(n \ne 2^k\),最后一层结点数为\(2^t\)(包括空着的),层数为\(\lfloor log_{2}n \rfloor + 1 + 1\)

所以线段树的层数为\(logn\)级别。

线段树的核心操作:

  1. query(root, l, r)
  2. modify(root, idx, val)
  3. build(root, l, r) // 在l,r区间上建立线段树
  4. pushup(root) //更新root结点维护的信息(max, min, ...)
  5. pushdown(带lazy标记的线段树)

简单版线段树

  1. 单点修改
  2. 区间查询(sum,max,min...)

原理:用线段树的结点来维护每一段区间,单点修改(递归修改,修改所有相关的区间),区间查询(递归查询返回sum),两者的复杂度\(O(logn)\)

模板题

给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列[a, b]的连续和。

输入格式

第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。

第二行包含 n 个整数,表示完整数列。

接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。

数列从1开始计数。

#include<iostream>
using namespace std;

const int N = 100010;

int a[N];
int n, m;

struct Node{
    int l, r;
    int sum;
}tr[N * 4];


void pushup(int u){
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r){
    if(l == r) tr[u] = {l, r, a[l]};
    else{
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

int modify(int u, int x, int v){
    if(tr[u].l == tr[u].r) tr[u].sum += v;
    else{
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

int query(int u, int l, int r){
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if(l <= mid) sum = query(u << 1, l, r);
    if(r > mid) sum += query(u << 1 | 1, l, r);
    
    return sum;
}

int main(){
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    build(1, 1, n);
    
    while(m --){
        int k, a, b;
        cin >> k >> a >> b;
        
        if(k) modify(1, a, b);
        else cout << query(1, a, b) << endl;
    }
    
    return 0;
}
posted @ 2020-09-23 20:49  yys_c  阅读(187)  评论(0编辑  收藏  举报