线段树学习前提须知
线段树的基础运用一般用于四个方面 单点修改 单点查询 区间修改 区间查询
且代码易于理解,虽代码量很长 但是并这不妨碍我们学习这个算法
算法内容
基本概念
1、线段树的形式类似于BST(二叉搜索树),在一些细节上区分于BST
2、线段树的基本思想:分治
3、每个节点都是以结构体的方式储存,结构体包含以下内容
-
当前节点所包含的区间的左端点、右端点。
-
这个区间所要维护的区间和(或其他 这里我们主要针对区间和)。
4、线段树的父亲节点都是其儿子节点的结合,其中根节点的区间就是整个数列
5、对于线段树:
-
在树上的一个节点 (k),存在 (k) 的子节点分别为 (k*2) 与 (k*2 + 1)。
-
对于这个节点 (k) ,它的左儿子的区间范围为 ([l, mid]) ,它的右儿子为 ([mid + 1, r])
线段树代码与思路
结构体
const int N = ???;
struct Node {
int l, r, w;
} tree[N << 2];
树记得开4倍空间
线段树的基础操作有 5 个:建树、单点查询、单点修改、区间查询、区间修改
建树
思路:
- 给每一个节点,赋予区间的左端点和右端点
- 对于每一个叶子节点,我们赋予它们已知的单独的值
- 利用线段树的性质,向上合并
void build(int k, int l, int r) {
tree[k].l = l; tree[k].r = r;
if(l == r) {
scanf("%d", &tree[k].w);
return ;
}
int mid = (l + r) >> 1;
build(k * 2, l, mid);
build(k * 2 + 1, mid + 1, r);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}
单点修改(我们这里假设需要修改的点编号为 X,需要添加的值为 T)
思路:
- 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
- 我们要修改的点一定在叶子节点上,查找这一部分需要消耗我们 (O(logn))
- 修改了一个点就会导致上面包含当前区间的点都要变,于是修改这一步也是 (O(logn))
//Now X and T has been entered;
void change_point(int k) {
if(tree[k].l == tree[k].r) {
tree[k].w += t;
return ;
}
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= x) change_point(k * 2);
else change_point(k * 2 + 1);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}
单点查询(这里设需要查询的点为 x)
思路:
- 和单点修改类似 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
//Now x has been entered;
int ans;
void ask_point(int k) {
if(tree[k].l == tree[k].r) {
ans = tree[k].w;
return ;
}
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= x) ask_point(k * 2);
else ask_point(k * 2 + 1);
}
区间查询(设查询的范围为 [l, r])
思路:
- 和单点查询的思路类似,但这里换成了区间
//Now l and r has been entered
int ans;
void ask_interval(int k, int l, int r) {
if(tree[k].l >= l && tree[k].r <= r) {
ans += tree[k].w;
return ;
}
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= l) ask_interval(k * 2, l, r);
if(mid < r) ask_interval(k * 2 + 1, l, r);
}
区间修改(※)
思路:
- 单纯的寻找和修改一定会超时,我们这里引进一个新的操作叫 Lazy(懒操作)
- 懒操作就像你打MC服务器,没有加载的区块是不会有任何行为的,但实际上这个区块上却存在方块和动物,当你加载了这个区块之后,上面的一些行为才正常化,比如熔炉烧东西或者是树苗长大。
- 懒操作实际就是一种差分的思想,把一些信息存储到一些点上,若这个点被遍历了,那么当前点的信息一定会被子节点所继承,其中子节点上若有其他信息,那这个信息是不会消失的
//Now l,r,x has been entered
struct Node {
int l, r, w;
int f;
} tree[N >> 2];
void Lazydown(int k) {
tree[k * 2].f += tree[k].f;
tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
tree[k * 2 + 1].f += tree[k].f;
tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
tree[k].f = 0;
}
void change_interval(int k) {
if(tree[k].l >= l && tree[k].r <= r) {
tree[k].w += (tree[k].r - tree[k].l + 1) * x;
tree[k].f += x;
return ;
}
if(tree[k].f) Lazydown(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= l) change_interval(k * 2);
if(mid < r) change_interval(k * 2 + 1);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}
有几个问题需要注意
- 懒标记的结构体需要更改,需要添加一个当前节点所存储的信息
- 这里我们发现修改变成了
tree[k].w += (tree[k].r - tree[k].l + 1) * x;
,这里你可以画图进行验证 - Lazydown中代表的意思就是,我们已经遍历了编号为 (k) 的点,那么已经遍历过了之后,肯定会将信息传递
- Lazydown的修改操作原理和区间修改中的相同,若不理解,可以画图进行深究
- 因为我们已经传递和遍历了,那么当前点的信息就会被清空
(不然又会回档了(游戏说法
加入了Lazydown之后,我们发现,只要遍历过的点都要进行懒操作,那么我们的所有代码都需要修改
全代码如下
//#define fre yes 信仰
#include <cstdio>
const int N = 200005;
struct Node {
int l, r, w;
int f;
} tree[N >> 2];
void build(int k, int l, int r) {
tree[k].l = l; tree[k].r = r;
if(l == r) {
scanf("%d", &tree[k].w);
return ;
}
int mid = (l + r) >> 1;
build(k * 2, l, mid);
build(k * 2 + 1, mid + 1, r);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w
}
void Lazydown(int k) {
tree[k * 2].f += tree[k].f;
tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
tree[k * 2 + 1].f += tree[k].f;
tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
tree[k].f = 0;
}
void change_point(int k, int x) {
if(tree[k].l == tree[k].r) {
tree[k].w += x;
return ;
}
if(tree[k].f) Lazydown(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= l) change_point(k * 2);
else change_point(k * 2 + 1);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}
int ans_p;
void ask_point(int k) {
if(tree[k].l == tree[k].r) {
ans_p = tree[k].w;
return ;
}
if(tree[k].f) Lazydown(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= l) ask_point(k * 2);
else ask_point(k * 2 + 1);
}
int ans_i;
void ask_interval(int k, int l, int r) {
if(tree[k].l >= l && tree[k].r <= r) {
ans_i += tree[k].w;
return ;
}
if(tree[k].f) Lazydown(k);
int mid = (tree[k].r - tree[k].l) >> 1;
if(mid >= l) ask_interval(k * 2, l, r);
if(mid < r) ask_interval(k * 2 + 1, l, r);
}
void change_interval(int k, int l, int r) {
if(tree[k].l >= l && tree[k].r <= r) {
tree[k].w += (tree[k].r - tree[k].l + 1) * x;
tree[k].f += x;
return ;
}
if(tree[k].f) Lazydown(k);
int mid = (tree[k].r - tree[k].l) >> 1;
if(mid >= l) change_interval(k * 2, l, r);
if(mid < r) chnage_interval(k * 2 + 1, l, r);
tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}
int main() {
...
}