原创建时间: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!