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