线段树 模板

纵然是瞬间 提交于 2019-12-02 12:25:00

原创建时间:2018-04-01 00:26:09

快速查找和修改区间

注:本文包含洛谷 P3372 【模板】线段树 1 题解

线段树模板

前言

  • 什么是线段树?

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

  • 线段树的主要用途及好处?

线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。

  • 线段树的应用?

最简单的应用就是记录线段是否被覆盖,随时查询当前被覆盖线段的总长度。

代码

基础函数

我们选择一个$O(1)$的取儿子函数:

inline int leftChild(int p) {
    return p << 1;
}
// 左子树 

inline int rightChild(int p) {
    return p << 1 | 1;
}
// 右子树 

线段树的维护:

void pushUp(int p) {
    t[p] = t[leftChild(p)] + t[rightChild(p)];
}
// 向上维护区间

void pushUpMin(int p) {
    t[p] = std::min(t[leftChild(p)], t[rightChild(p)]);
} 
// 向t[p]下放Min标签

void pushUpMax(int p) {
    t[p] = std::max(t[leftChild(p)], t[rightChild(p)]);
} 
// 向t[p]下放Max标签

递归建树:

typedef long long int lli;

void buildTree(lli p, lli l, lli r) {
    if (l == r) {
        ans[p] = a[l];
        return;
    }
    // 如果左右区间相同,则必是叶子节点
    lli mid = (l + r) >> 1;
    buildTree(leftChild(p), l, mid);
    buildTree(rightChild(p), mid + 1, r);
    // 递归
    pushUp(p); 
} 
// 递归 + 二分建树

区间修改函数

inline void Record(lli p, lli l, lli r, lli k) {
    tag[p] = tag[p] + k;
    ans[p] = ans[p] + k * (r - l + 1);
    // 因为是区间统一改变,所以ans要加元素个数 
}
// 记录当前节点所代表的区间

inline void pushDown(lli p, lli l, lli r) {
    lli mid = (l + r) >> 1;
    Record(leftChild(p), l, mid, tag[p]);
    Record(rightChild(p), mid + 1, r, tag[p]);
    tag[p] = 0;
    // 每次更新两个儿子节点,不断向下传递 
} 

inline void update(lli nl, lli nr, lli l, lli r, lli p, lli k) {
    // 将要修改从 nl 到 nr 的区间
    // l,r 为当前节点所储存的区间 
    // p 为当前节点的编号
    if (nl <= l && r <= nr) {
        ans[p] += k * (r - l + 1);
        tag[p] += k;
        return;
    } 
    pushDown(p, l, r);  
    lli mid = (l + r) >> 1;
    if (nl <= mid) update(nl, nr, l, mid, leftChild(p), k)
    if (nr > mid) update(nl, nr,mid + 1, r, rightChild(p), k);
    pushUp(p);
}
// 更新区间 

查询区间函数

inline lli query(lli qx, lli qy, lli l, lli r, lli p) {
    lli res = 0;
    if (qx <= l && r <= qy) return ans[p];
    lli mid = (l + r) >> 1;
    pushDown(p, l, r);
    if (qx <= mid) res += query(qx, qy, l, mid, leftChild(p));
    if (mid + 1 <= qy) res += query(qx, qy, mid + 1, r, rightChild(p));
    return res; 
}
// 查询区间 

依然采用二分的形式...

洛谷 P3372 题解

题目描述

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

1.将某区间每一个数加上x

2.求出某区间每一个数的和

输入格式

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

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

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

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出格式

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

输入样例

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

输出样例

11
8
20

解题思路

就是把上面的函数都复制下来就行了= =

没什么多解释的

注释见上面代码

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
// using namespace std;

typedef long long int ll;
typedef unsigned long long int ull;

const int MAXN = 1000000 + 1;

ull n, m, a[MAXN], ans[MAXN << 2], tag[MAXN << 2];

inline ll ls(ll x) {
    return x << 1; 
}

inline ll rs(ll x) {
    return x << 1 | 1;
}

void scan(){
    scanf("%lld %lld", &n, &m);
    for (ll i = 1;i <= n;i++) {
        scanf("%lld", &a[i]);
    }
}

inline void pushUp(ll p) {
    ans[p] = ans[ls(p)] + ans[rs(p)];
} 

inline void build(ll p, ll l, ll r) {
    tag[p] = 0;
    if (l == r) {
        ans[p] = a[l];
        return; 
    }
    ll mid = (l + r) >> 1;
    build(ls(p), l, mid);
    build(rs(p), mid + 1, r);
    pushUp(p);
}

inline void rec(ll p, ll l, ll r, ll k) {
    tag[p] = tag[p] + k;
    ans[p] = ans[p] + k * (r - l + 1);
}

inline void pushDown(ll p,ll l, ll r) {
    ll mid = (l + r) >> 1;
    rec(ls(p), l, mid, tag[p]);
    rec(rs(p), mid + 1, r, tag[p]);
    tag[p] = 0;
}

inline void update(ll nl, ll nr, ll l, ll r, ll p, ll k) {
    if (nl <= l && r <= nr) {
        ans[p] += k * (r - l + 1);
        tag[p] += k;
        return;
    }
    pushDown(p, l, r);
    ll mid = (l + r) >> 1;
    if (nl <= mid) update(nl, nr, l, mid, ls(p), k);
    if (nr > mid) update(nl, nr, mid + 1, r, rs(p), k);
    pushUp(p);
}

ll query(ll qx, ll qy, ll l, ll r, ll p) {
    ll res = 0;
    if (qx <= l && r <= qy) return ans[p];
    ll mid = (l + r) >> 1;
    pushDown(p, l, r);
    if (qx <= mid) res += query(qx, qy, l, mid, ls(p));
    if (qy > mid) res += query(qx, qy, mid + 1, r, rs(p));
    return res;
}

int main() {
    // ios::sync_with_stdio(false);
    ll a1, b, c, d, e, f;
    scan();
    build(1, 1, n);
    while (m--) {
        scanf("%lld", &a1);
        switch(a1) {
            case 1:{
                scanf("%lld %lld %lld", &b, &c, &d);
                update(b, c, 1, n, 1, d);
                break;
            }
            case 2:{
                scanf("%lld %lld", &e, &f);
                printf("%lld\n", query(e, f, 1, n, 1)); 
                break;
            }
        }
    }
    return 0;
}
// 注意一下,stdio 和 iostream 混用会出现很多奇怪的bug!
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!