线段树讲解

目录

题目描述:

维护父节点的值:

建树:

区间修改计算函数:

下放lazy值的函数:

区间修改函数:

区间查询函数:

完整代码:


题目描述:

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k。
  2. 求出某区间每一个数的和。

输入格式:

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

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 或 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [x,y] 内每个数加上 k。
  2. 2 x y:输出区间 [x,y] 内每个数的和。

输出格式:

输出包含若干行整数,即为所有操作 2 的结果。

维护父节点的值:

//父节点的值为两子节点的值之和
void pushup(ll pos) {
    sum[pos] = sum[pos * 2] + sum[pos * 2 + 1];
}

建树:

//sum数组为子节点的和
void build(ll pos, ll l, ll r) {
    //初始化lazy数组
    lazy[pos] = 0;
    //若该节点为叶子节点,则sum中存储的为该节点的值
    if(l == r) {
        sum[pos] = a[l];
        return;
    }
    //若不为叶子节点,则递归调用函数,直至叶子节点
    ll mid = (l + r) / 2;
    build(pos * 2, l, mid);
    build(pos * 2 + 1, mid + 1, r);
    //若该节点不为叶子节点,则更新该节点的值
    //使得该点为两子节点的和
    pushup(pos);
}

区间修改计算函数:

//lazy保存某个节点及其子节点是否被修改过
//若为零,则未被修改
//若不为零,则在该范围内的每个叶子节点均需加上lazy的值
//(此时还未修改,只是存储在这,需要等到lazy从不为零的值变为零,才修改了这段区间的值)
void f(ll pos, ll l, ll r, ll num) {
    //将lazy下放,因为可能存在之前的lazy未被使用
    //故需要进行加法计算,而不是直接赋值
    lazy[pos] += num;
    //该节点可能不为叶子节点,长度不一定为1,故应加上lazy的值乘以长度
    //修改了sum数组的值,但不一定修改了每个节点的值
    sum[pos] += num * (r - l + 1);
}

下放lazy值的函数:

void pushdown(ll pos, ll l, ll r) {
    //以该点为根节点,保证每个节点都能被修改到
    ll mid = (l + r) / 2;
    f(pos * 2, l, mid, lazy[pos]);
    f(pos * 2 + 1, mid + 1, r, lazy[pos]);
    //使用lazy数组将子节点的值更新后,需将lazy置零,避免再次使用
    lazy[pos] = 0;
}

区间修改函数:

void update(ll nl, ll nr, ll l, ll r, ll pos, ll num) {
    //若需要改变的范围完全覆盖此时查询的范围
    if(nl <= l && r <= nr) {
        //将sum数组及时更新
        sum[pos] += num * (r - l + 1);
        //先不着急改变左右子节点的值
        //用lazy数组存储该节点修改的值
        lazy[pos] += num;
        return;
    }
    //将lazy下放
    pushdown(pos, l, r);
    ll mid = (l + r) / 2;
    //若与左子节点有交集,则向左进行判断
    if(nl <= mid)
        update(nl, nr, l, mid, pos * 2, num);
    //同理,若与右子节点有交集,则向右进行判断
    if(nr > mid)
        update(nl, nr, mid + 1, r, pos * 2 + 1, num);
    //该点的子节点可能已经改变,故更新该节点的值
    pushup(pos);
}

区间查询函数:

ll query(ll nl, ll nr, ll l, ll r, ll pos) {
    ll res = 0;
    //若查询的范围完全覆盖此时遍历的范围
    //若查询的范围合法,则该返回值是为了递归调用做准备
    if(nl <= l && r <= nr)
        return sum[pos];
    ll mid = (l + r) / 2;
    //使用之前存储的lazy
    pushdown(pos, l, r);
    if(nl <= mid)
        res += query(nl, nr, l, mid, pos * 2);
    if(nr > mid)
        res += query(nl, nr, mid + 1, r, pos * 2 + 1);
    return res;
}

完整代码:

#include 
using namespace std;
#define ll long long
const int N = 1e5 + 10;
ll n, m, a[N], flag, x, y, k, sum[N * 4], lazy[N * 4];

void pushup(ll pos) {
    sum[pos] = sum[pos * 2] + sum[pos * 2 + 1];
}

void build(ll pos, ll l, ll r) {
    lazy[pos] = 0;
    if(l == r) {
        sum[pos] = a[l];
        return;
    }
    ll mid = (l + r) / 2;
    build(pos * 2, l, mid);
    build(pos * 2 + 1, mid + 1, r);
    pushup(pos);
}

void f(ll pos, ll l, ll r, ll num) {
    lazy[pos] += num;
    sum[pos] += num * (r - l + 1);
}

void pushdown(ll pos, ll l, ll r) {
    ll mid = (l + r) / 2;
    f(pos * 2, l, mid, lazy[pos]);
    f(pos * 2 + 1, mid + 1, r, lazy[pos]);
    lazy[pos] = 0;
}

void update(ll nl, ll nr, ll l, ll r, ll pos, ll num) {
    if(nl <= l && r <= nr) {
        sum[pos] += num * (r - l + 1);
        lazy[pos] += num;
        return;
    }
    pushdown(pos, l, r);
    ll mid = (l + r) / 2;
    if(nl <= mid)
        update(nl, nr, l, mid, pos * 2, num);
    if(nr > mid)
        update(nl, nr, mid + 1, r, pos * 2 + 1, num);
    pushup(pos);
}

ll query(ll nl, ll nr, ll l, ll r, ll pos) {
    ll res = 0;
    if(nl <= l && r <= nr)
        return sum[pos];
    ll mid = (l + r) / 2;
    pushdown(pos, l, r);
    if(nl <= mid)
        res += query(nl, nr, l, mid, pos * 2);
    if(nr > mid)
        res += query(nl, nr, mid + 1, r, pos * 2 + 1);
    return res;
}

int main() {
    scanf("%lld %lld", &n, &m);
    for(ll i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    //从最开始构建树
    build(1, 1, n);
    while(m--) {
        scanf("%lld", &flag);
        if(flag == 1) {
            scanf("%lld %lld %lld", &x, &y, &k);
            update(x, y, 1, n, 1, k);
        }
        else {
            scanf("%lld %lld", &x, &y);
            printf("%lld\n", query(x, y, 1, n, 1));
        }
    }
    return 0;
}

你可能感兴趣的:(数据结构,线段树,c++,算法)