Segment Tree
十分朴素的树。
线段树是一种及其常用的数据结构。
它面对各种类型的数据都有应付的方式。
而且不断有各种 「线段树·改」,使得其功能更为强大。
我对此深有体会。
以前我总喜欢用树状数组代替线段树,对于线段树这个数据结构我是抗拒的。反正都能用干嘛要用麻烦的。
但是线段树的功能之完善程度不是一般强,而是强到离谱。堪称学了之后收益最大的数据结构。
维护数据?
作为一钟数据结构,其目的自然是维护某种类型的数据。
一般线段树的每个结点都表示了一个区间。
线段树是基于分治思想的。
观察一下,这显然是一棵完全二叉树。
那么建树的过程也就特别显而易见了。
建树的过程大致如下:
- 向下递归;
- 如果当前结点是叶子结点,存储数据;
- 由子结点的值更新当前结点的值。
//更新
void push_up(int now) {
int L_son=now*2, R_son=now*2+1;
tree[now].data=tree[L_son].data+tree[R_son].data;
}
//建树
void build(int now, int L, int R) {
tree[now].L=L; tree[now].R=R; tree[now].len=R-L+1;
if (L==R) {
tree[now].data=a[L];
return ;
}
int mid=(L+R)>>1;
int L_son=now*2, R_son=now*2+1;
build(L_son, L, mid); build(R_son, mid+1, R);
push_up(now);
}
单点修改&单点查询很简单,这里就不赘述了。
区间查询和建树差不多,只是没有遍历整棵树。因此进行区间查询操作时需要判断往哪棵子树走。
//区间查询
int query(int now, int L, int R) {
if (L<=tree[now].L && R>=tree[now].R) {
return tree[now].data;
}
push_down(now);
int mid=(tree[now].L+tree[now].R)>>1, res=0;
int L_son=now*2, R_son=now*2+1;
if (L<=mid) {
res+=query(L_son, L, R);
}
if (R>mid) {
res+=query(R_son, L, R);
}
return res;
}
区间修改…
一个个位置单点修改?显然不可能,这样的复杂度是无法接受的。
这里就需要引入懒标记(延迟标记)的概念了。其实对于它的操作早就出现了(就是上面的 push_down,意为下传懒标记(push down lazy tag))
对于需要更新的值,我们打上标记,等需要时再进行修改,如此效率就得到了保证。
事实上区间修改实现起来和区间查询很像。(事实上每个操作都差不多,毕竟都是沿着树向下走)
//下传懒标记
void push_down(int now) {
int lazy_tag=tree[now].tag;
tree[now].tag=0;
int L_son=now*2, R_son=now*2+1;
if (lazy_tag) {
tree[L_son].data+=lazy_tag*(tree[L_son].R-tree[L_son].L+1);
tree[R_son].data+=lazy_tag*(tree[now].len);
tree[L_son].tag+=lazy_tag;
tree[R_son].tag+=lazy_tag;
}
}
//区间修改
void update(int now, int L, int R, int val) {
if (L<=tree[now].L && R>=tree[now].R) {
tree[now].data+=val*(tree[now].R-tree[now].L+1);
tree[now].tag+=val;
return ;
}
push_down(now);
int mid=(tree[now].L+tree[now].R)>>1;
int L_son=now*2, R_son=now*2+1;
if (L<=mid) {
update(L_son, L, R, val);
}
if (R>mid) {
update(R_son, L, R, val);
}
push_up(now);
}
c o d e : code: code:
#include <stdio.h>
#include <ctype.h>
#pragma GCC diagnostic error "-std=gnu++11"
#define reg register
using namespace std;
namespace fast_IO {
template <typename conv>
inline conv read(conv &x) {
char ch=getchar(); int flag=1; x=0;
while (!isdigit(ch)) {if (ch=='-') flag=-flag; ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-48; ch=getchar();}
x*=flag;
}
template <typename conv, typename ... Args>
inline conv read(conv &first, Args & ... args) {
read(first);
read(args...);
}
template <typename conv>
inline conv write(conv x) {
if (x<0) {putchar('-'); x=-x;}
if (x>9) {write(x/10);}
putchar(x%10+48);
}
template <typename conv, typename ... Args>
inline conv write(conv first, Args ... args) {
write(first);
write(args...);
}
}
using namespace fast_IO;
const int maxn=1e6+5;
int n, m;
int a[maxn];
struct segment_tree {
int L, R, len;
int data;
int tag; /*addition*/
} tree[maxn<<2];
//更新
void push_up(int now) {
int L_son=now*2, R_son=now*2+1;
tree[now].data=tree[L_son].data+tree[R_son].data;
}
//建树
void build(int now, int L, int R) {
tree[now].L=L; tree[now].R=R; tree[now].len=R-L+1;
if (L==R) {
tree[now].data=a[L];
return ;
}
int mid=(L+R)>>1;
int L_son=now*2, R_son=now*2+1;
build(L_son, L, mid); build(R_son, mid+1, R);
push_up(now);
}
//下传懒标记
void push_down(int now) {
int lazy_tag=tree[now].tag;
tree[now].tag=0;
int L_son=now*2, R_son=now*2+1;
if (lazy_tag) {
tree[L_son].data+=lazy_tag*(tree[L_son].R-tree[L_son].L+1);
tree[R_son].data+=lazy_tag*(tree[now].len);
tree[L_son].tag+=lazy_tag;
tree[R_son].tag+=lazy_tag;
}
}
//单点查询
int query(int now, int pos) {
if (tree[now].L==tree[now].R) {
return tree[now].data;
}
push_down(now);
int mid=(tree[now].L+tree[now].R)>>1;
int L_son=now*2, R_son=now*2+1;
if (pos<=mid) {
return query(L_son, pos);
}
else {
return query(R_son, pos);
}
}
//区间查询
int query(int now, int L, int R) {
if (L<=tree[now].L && R>=tree[now].R) {
return tree[now].data;
}
push_down(now);
int mid=(tree[now].L+tree[now].R)>>1, res=0;
int L_son=now*2, R_son=now*2+1;
if (L<=mid) {
res+=query(L_son, L, R);
}
if (R>mid) {
res+=query(R_son, L, R);
}
return res;
}
//单点修改
void update(int now, int pos, int val) {
if (tree[now].L==tree[now].R) {
tree[now].data+=val;
return ;
}
int mid=(tree[now].L+tree[now].R)>>1;
int L_son=now*2, R_son=now*2+1;
if (pos<=mid) {
update(L_son, pos, val);
}
else {
update(R_son, pos, val);
}
push_up(now);
}
//区间修改
void update(int now, int L, int R, int val) {
if (L<=tree[now].L && R>=tree[now].R) {
tree[now].data+=val*(tree[now].R-tree[now].L+1);
tree[now].tag+=val;
return ;
}
push_down(now);
int mid=(tree[now].L+tree[now].R)>>1;
int L_son=now*2, R_son=now*2+1;
if (L<=mid) {
update(L_son, L, R, val);
}
if (R>mid) {
update(R_son, L, R, val);
}
push_up(now);
}
int main() {
read(n, m);
for (reg int i=1; i<=n; i++) {
read(a[i]);
}
build(1, 1, n);
for (reg int i=1; i<=m; i++) {
int op, x, y, k;
read(op);
if (op==1) {
read(x, y, k);
update(1, x, y, k);
}
else {
read(x, y);
write(query(1, x, y)); putchar('\n');
}
}
return 0;
}
普通线段树就此落幕。
但是线段树绝不是仅此而已。
线段树需要开的4倍的空间,这在很多时候是无法接受的。
这就需要用到 线段树进阶——动态开点 啦。