「学习笔记」普通线段树
Segment Tree
十分朴素的树。
线段树是一种及其常用的数据结构。
它面对各种类型的数据都有应付的方式。
而且不断有各种 「线段树·改」,使得其功能更为强大。
我对此深有体会。
以前我总喜欢用树状数组代替线段树,对于线段树这个数据结构我是抗拒的。反正都能用干嘛要用麻烦的。
但是线段树的功能之完善程度不是一般强,而是强到离谱。堪称学了之后收益最大的数据结构。
维护数据?
作为一钟数据结构,其目的自然是维护某种类型的数据。
一般线段树的每个结点都表示了一个区间。

线段树是基于分治思想的。
观察一下,这显然是一棵完全二叉树。
那么建树的过程也就特别显而易见了。
建树的过程大致如下:
- 向下递归;
- 如果当前结点是叶子结点,存储数据;
- 由子结点的值更新当前结点的值。
//更新
void push_up(int now) {
int left_son=now*2, right_son=now*2+1;
tree[now].data=tree[left_son].data+tree[right_son].data;
}
//建树
void build(int now, int left, int right) {
tree[now].left=left; tree[now].right=right; tree[now].length=right-left+1;
//判断是否为叶结点,是则存储数据
if (left==right) {
tree[now].data=a[left];
return ;
}
int middle=(left+right)>>1;
int left_son=now*2, right_son=now*2+1;
build(left_son, left, middle); build(right_son, middle+1, right);
push_up(now);//更新当前结点的值
}
单点修改&单点查询很简单,这里就不赘述了。
区间查询和建树差不多,只是没有遍历整棵树。因此进行区间查询操作时需要判断往哪棵子树走。
//区间查询
int query(int now, int left, int right) {
if (left<=tree[now].left && right>=tree[now].right) {
return tree[now].data;
}
push_down(now);
//判断要往哪个区间走
int middle=(tree[now].left+tree[now].right)>>1, res=0;
int left_son=now*2, right_son=now*2+1;
if (left<=middle) {
res+=query(left_son, left, right);
}
if (right>middle) {
res+=query(right_son, left, right);
}
return res;
}
区间修改…
一个个位置单点修改?显然不可能,这样的复杂度是无法接受的。
这里就需要引入懒标记(延迟标记)的概念了。其实对于它的操作早就出现了(就是上面的 push_down,意为下传懒标记(push down lazy tag))
对于需要更新的值,我们打上标记,等需要时再进行修改,如此效率就得到了保证。
事实上区间修改实现起来和区间查询很像。(事实上每个操作都差不多,毕竟都是沿着树向下走)
//下传懒标记
void push_down(int now) {
int lazy_tag=tree[now].tag;
tree[now].tag=0;
int left_son=now*2, right_son=now*2+1;
if (lazy_tag) {
tree[left_son].data+=lazy_tag*(tree[left_son].right-tree[left_son].left+1);
tree[right_son].data+=lazy_tag*(tree[now].length);
tree[left_son].tag+=lazy_tag;
tree[right_son].tag+=lazy_tag;
}
}
//区间修改
void update(int now, int left, int right, int value) {
if (left<=tree[now].left && right>=tree[now].right) {
tree[now].data+=value*(tree[now].right-tree[now].left+1);
tree[now].tag+=value;
return ;
}
push_down(now);
int middle=(tree[now].left+tree[now].right)>>1;
int left_son=now*2, right_son=now*2+1;
if (left<=middle) {
update(left_son, left, right, value);
}
if (right>middle) {
update(right_son, left, right, value);
}
push_up(now);
}
#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 left, right, length;
int data;
int tag; /*addition*/
} tree[maxn<<2];
//更新
void push_up(int now) {
int left_son=now*2, right_son=now*2+1;
tree[now].data=tree[left_son].data+tree[right_son].data;
}
//建树
void build(int now, int left, int right) {
tree[now].left=left; tree[now].right=right; tree[now].length=right-left+1;
if (left==right) {
tree[now].data=a[left];
return ;
}
int middle=(left+right)>>1;
int left_son=now*2, right_son=now*2+1;
build(left_son, left, middle); build(right_son, middle+1, right);
push_up(now);
}
//下传懒标记
void push_down(int now) {
int lazy_tag=tree[now].tag;
tree[now].tag=0;
int left_son=now*2, right_son=now*2+1;
if (lazy_tag) {
tree[left_son].data+=lazy_tag*(tree[left_son].right-tree[left_son].left+1);
tree[right_son].data+=lazy_tag*(tree[now].length);
tree[left_son].tag+=lazy_tag;
tree[right_son].tag+=lazy_tag;
}
}
//单点查询
int query(int now, int pos) {
if (tree[now].left==tree[now].right) {
return tree[now].data;
}
push_down(now);
int middle=(tree[now].left+tree[now].right)>>1;
int left_son=now*2, right_son=now*2+1;
if (pos<=middle) {
return query(left_son, pos);
}
else {
return query(right_son, pos);
}
}
//区间查询
int query(int now, int left, int right) {
if (left<=tree[now].left && right>=tree[now].right) {
return tree[now].data;
}
push_down(now);
int middle=(tree[now].left+tree[now].right)>>1, result=0;
int left_son=now*2, right_son=now*2+1;
if (left<=middle) {
result+=query(left_son, left, right);
}
if (right>middle) {
result+=query(right_son, left, right);
}
return result;
}
//单点修改
void update(int now, int pos, int value) {
if (tree[now].left==tree[now].right) {
tree[now].data+=value;
return ;
}
int middle=(tree[now].left+tree[now].right)>>1;
int left_son=now*2, right_son=now*2+1;
if (pos<=middle) {
update(left_son, pos, value);
}
else {
update(right_son, pos, value);
}
push_up(now);
}
//区间修改
void update(int now, int left, int right, int value) {
if (left<=tree[now].left && right>=tree[now].right) {
tree[now].data+=value*(tree[now].right-tree[now].left+1);
tree[now].tag+=value;
return ;
}
push_down(now);
int middle=(tree[now].left+tree[now].right)>>1;
int left_son=now*2, right_son=now*2+1;
if (left<=middle) {
update(left_son, left, right, value);
}
if (right>middle) {
update(right_son, left, right, value);
}
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倍的空间,
这在很多时候是无法接受的。
这就需要用到线段树进阶——动态开点了。

浙公网安备 33010602011771号