树状数组
树状数组,顾名思义,是长的像一棵树的数组,那具体的结构是怎么样的呢?这里以区间和为例
具体我们要怎么实现呢?如何来建立两个数组之间的关系?
或许你这么看就明白了
没错,就是找到最下位的1,加到原来的下标上就能得到它的父亲结点下标
举几个例子1+lowbit(1)=2 2+lowbit(2)=4 3+lowbit(3)=4
那么我们的lowbit应该怎么写呢?
int lowbit(int n){
return n&(-n);
}
是不是很简单,也非常的好记。大家可以从补码和反码的角度去理解,我这里就不多赘述了。
接下来讲代码实现
void update(int i, int add,int n) {//添加结点,值为add
while (i <=n) {
c[i] += add;
i += lowbit(i);
}
}
int getsum(int x) {//获取1到x的结点值的和
int res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
这里实现的是树状数组最基础的两个功能,我们的建树过程只要一个for循环来添加结点就可以实现,理解到这里,获取1到x结点的功能也就很简单了。
而实现了获取1到x的功能,我们就可以利用这个求任意区间的和了。
这里做一下模板题模板题1
普通树状数组可以实现区间查询,单点修改
高级树状数组也可以实现区间修改,区间查询
(27条消息) 【小结】树状数组的区间修改与区间查询_每天心塞一点点-CSDN博客_树状数组区间修改区间查询
线段树
普通树状数组所实现的功能完全被线段树所覆盖,线段树的功能更广,但是普通线段树在空间复杂度上,比树状数组要大很多。
那么要怎么实现呢?
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
先看一下线段树的结构,下面给出线段树的结构图
区间求和的线段树的结构图
红色数字的表示他们在线段树数组中存储的位置下标。这个位置关系要怎么理解呢?众所周知,在二叉树中,父节点的下标2就是左孩子的下标,父节点的下标2+1就是右孩子的下标。
下面先给出树的结构体
struct node
{
int l, r;//l代表左孩子下标,r代表右孩子下标
long long w, mark;//记录左孩子,右孩子,区间和,还有延时标记
}tree[N << 2];//开四倍数组
建树代码
int buildtree(int rt, int l, int r) {
tree[rt].l = l; tree[rt].r = r;
if (l == r) {//说明到达叶子结点
scanf("%lld", &tree[rt].w);
return 1;
}
int m = (l + r) / 2;
buildtree(rt << 1, l, m);//创建左结点
buildtree(rt << 1 | 1, m + 1, r);//创建右节点
tree[rt].w = tree[rt << 1].w + tree[rt << 1 | 1].w;//汇总子区间和
return 1;
}
区间查询的代码
long long ask_section(int rt, int l, int r) {//区间查询
if (l <= tree[rt].l && tree[rt].r <= r)
return tree[rt].w;
if (tree[rt].mark)down(rt);//这里看不懂没关系,先忽略它
int mid = (tree[rt].l + tree[rt].r) >> 1;
long long ans = 0;
if (r <= mid)
ans += ask_section(rt << 1, l, r);
else if (l > mid)
ans += ask_section(rt << 1 | 1, l, r);
else {
ans += (ask_section(rt << 1, l, r) + ask_section(rt << 1 | 1, l, r));
}
return ans;
}
大家现在想一想,如果我要进行区间修改的话,是不是要先找到每一个叶子结点,然后逐一进行修改,如果这样的话,我们的区间修改的复杂度可比一般的数组的区间修改还要高,所以我们的前辈们创造了懒惰标记
下面是懒惰标记下传的代码和区间修改的代码
void section_change(int rt, int l, int r, int mark) {//区间修改
if (l <= tree[rt].l && tree[rt].r <= r) {
tree[rt].mark += mark;
tree[rt].w += (tree[rt].r - tree[rt].l + 1) * mark;
return;
}
if (tree[rt].mark)down(rt);
int mid = (tree[rt].l + tree[rt].r) >> 1;
if (r <= mid)section_change(rt << 1, l, r, mark);
else if (l > mid)section_change(rt << 1 | 1, l, r, mark);
else {
section_change(rt << 1, l, r, mark);
section_change(rt << 1 | 1, l, r, mark);
}
tree[rt].w = tree[rt << 1].w + tree[rt << 1 | 1].w;
}
然后就可以做模板题了模板题1
下面是全部的代码
#include<bits/stdc++.h>//线段树(区间和)
using namespace std;
const int N = 100000 + 10;//用位运算速度快
struct node
{
int l, r;
long long w, mark;//记录左孩子,右孩子,区间和,还有延时标记
}tree[N << 2];//开四倍数组
int buildtree(int rt, int l, int r) {
tree[rt].l = l; tree[rt].r = r;
if (l == r) {//说明到达叶子结点
scanf("%lld", &tree[rt].w);
return 1;
}
int m = (l + r) / 2;
buildtree(rt << 1, l, m);//创建左结点
buildtree(rt << 1 | 1, m + 1, r);//创建右节点
tree[rt].w = tree[rt << 1].w + tree[rt << 1 | 1].w;//汇总子区间和
return 1;
}
int down(int rt) {//下传标记
tree[rt << 1].mark += tree[rt].mark; tree[rt << 1 | 1].mark += tree[rt].mark;
tree[rt << 1].w += tree[rt].mark * (tree[rt << 1].r - tree[rt << 1].l + 1);
tree[rt << 1 | 1].w += tree[rt].mark * (tree[rt << 1 | 1].r - tree[rt << 1 | 1].l + 1);
tree[rt].mark = 0;
return 1;
}
long long ask_section(int rt, int l, int r) {//区间查询
if (l <= tree[rt].l && tree[rt].r <= r)
return tree[rt].w;
if (tree[rt].mark)down(rt);
int mid = (tree[rt].l + tree[rt].r) >> 1;
long long ans = 0;
if (r <= mid)
ans += ask_section(rt << 1, l, r);
else if (l > mid)
ans += ask_section(rt << 1 | 1, l, r);
else {
ans += (ask_section(rt << 1, l, r) + ask_section(rt << 1 | 1, l, r));
}
return ans;
}
void section_change(int rt, int l, int r, int mark) {//区间修改
if (l <= tree[rt].l && tree[rt].r <= r) {
tree[rt].mark += mark;
tree[rt].w += (tree[rt].r - tree[rt].l + 1) * mark;
return;
}
if (tree[rt].mark)down(rt);
int mid = (tree[rt].l + tree[rt].r) >> 1;
if (r <= mid)section_change(rt << 1, l, r, mark);
else if (l > mid)section_change(rt << 1 | 1, l, r, mark);
else {
section_change(rt << 1, l, r, mark);
section_change(rt << 1 | 1, l, r, mark);
}
tree[rt].w = tree[rt << 1].w + tree[rt << 1 | 1].w;
}
int main() {
int n, m;
cin >> n >> m;
buildtree(1, 1, n);
while (m--) {
int t, l, r, k;
scanf("%d", &t);
switch (t)
{
case 1:scanf("%d%d%d", &l, &r, &k);
section_change(1, l, r, k);
break;
case 2:
scanf("%d%d", &l, &r);
printf("%lld\n", ask_section(1, l, r));
break;
}
}
}
在学习了线段树以后,可以学一些线段树的高级用法,可持久化线段树(主席树),ZKW线段树
(21条消息) 线段树详解_ling_wang的博客-CSDN博客
进阶线段树内容:主席树,ZKW线段树(实现空间上的压缩)
(24条消息) zkw线段树详解_keshuqi的博客-CSDN博客_zkw线段树
树上莫队
······