线段树和IndexTree都是用来解决数组范围累加和问题
我们知道数组的前缀和可以立刻得出某范围的累加和,但是如果数组中的某个值一旦改变,那么前缀和数组就需要重新计算,这样的时间复杂度是O(n)
线段树和IndexTree的时间复杂度达到O(logn)
线段树和IndexTree的区别在于:线段树可以做到范围更新,而IndexTree只能做到单点更新
但是IndexTree相比于线段树更好改成二维和三维
接下来是线段树的代码实现:
#include<iostream>
#include<unordered_map>
#include<vector>
using namespace std;
//线段树
//线段树就是利用惰性信息,如果任务包含住了当前的范围,就不需要对下面进行更新,从而达到加速的效果
class SegmentTree {
private:
int* sum;//存储对应范围内的累加和的数组
int* lazy;//累加和的惰性信息
int* change;
bool* update;//和change配合使用,当进行范围更新时,这个惰性信息不能只用一个数组,因为可能更新成0
int Maxsize;//对于要查询的数组,要等于其4倍的大小,使得能足够放下
int* arr;//下标规定是从1开始的
public:
SegmentTree(vector<int>& nums) {
Maxsize = nums.size() << 2;
sum = new int[Maxsize]();
lazy = new int[Maxsize]();
change = new int[Maxsize]();
update = new bool[Maxsize]();
arr = new int[nums.size() + 1]();
for (int i = 1; i < nums.size() + 1; i++) {
arr[i] = nums[i - 1];
}
}
~SegmentTree() {
delete[] sum;
delete[] lazy;
delete[] change;
delete[] update;
delete[] arr;
}
void build(int l,int r,int rt) {
//在l-r这个范围内去初始化
if (l == r) {
sum[rt] = arr[l];
return;
}
int mid = l + (r - l) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 + 1);
pushUp(rt);
}
void pushUp(int rt) {
//求得sum[rt]的累加和,等于左孩子加上右孩子
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void pushDown(int ln, int rn, int rt) {
//ln表示左子树的结点个数
//rn表示右子树的结点个数
//必须先更新,更新优先级高,一旦更新了,那么之前所保留下来的lazy惰性信息就失效了
//lazy信息是针对之前没更新前的
if (update[rt]) {
update[rt << 1] = true;
update[rt << 1 | 1] = true;
change[rt << 1] = change[rt];
change[rt << 1 | 1] = change[rt];
sum[rt << 1] = change[rt] * ln;
sum[rt << 1 | 1] = change[rt] * rn;
lazy[rt << 1] = 0;
lazy[rt << 1 | 1] = 0;
//change[rt] = 0;//这一步可以忽略不写,并不像lazy惰性一样需要累加,这是直接改变
update[rt] = false;
}
if (!lazy[rt]) {
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
sum[rt << 1] += lazy[rt] * ln;
sum[rt << 1 | 1] += lazy[rt] * rn;
lazy[rt] = 0;
}
}
void add(int L, int R, int C, int l, int r, int rt) {
//任务:在arr[L]到arr[R]范围上增加C
//l和r是sum[rt]所表示的范围
//若任务包含了rt所表示的范围,则进行惰性增加
if (L <= l && R >= r) {
lazy[rt] += C;
sum[rt] += C * (r - l + 1);
return;
}
int mid = l + (r - l) >> 1;
pushDown(mid - L + 1, R - mid, rt);
if (L <= mid) {
add(L, R, C, l, mid, rt << 1);
}
if (R > mid) {
add(L, R, C, mid + 1, r, rt << 1 | 1);
}
pushUp(rt);
}
void Update(int L, int R, int C, int l, int r, int rt) {
if (L <= l && R >= r) {
update[rt] = true;
change[rt] = C;
sum[rt] = C * (r - l + 1);
lazy[rt] = 0;
return;
}
int mid = l + (r - l) >> 1;
pushDown(mid - L + 1, R - mid, rt);
if (L <= mid) {
Update(L, R, C, l, mid, rt << 1);
}
if (R > mid) {
Update(L, R, C, mid + 1, r, rt << 1 | 1);
}
pushUp(rt);
}
int query(int L, int R, int l, int r, int rt) {
if (L <= l && R >= r) {
return sum[rt];
}
int mid = l + (r - l) >> 1;
pushDown(mid - L + 1, R - mid, rt);
int ans = 0;
if (L <= mid) {
ans += query(L, R, l, mid, rt << 1);
}
if (R > mid) {
ans += query(L, R, mid + 1, r, rt << 1 | 1);
}
return ans;
}
};
接下来是IndexTree,其只能做到单点更新,而且查询只能查1~所给定范围,若想查特定范围,那就自行进行相减;初始化也自己写一下,就是每次都是add你给定数组的值
//IndexTree
class IndexTree {
private:
int* tree;//辅助数组,累加和就是存在这个数组中,但是它有点不一样,从下标1开始
int MaxN;
int* arr;
public:
IndexTree(vector<int>& nums) {
MaxN = nums.size() + 1;
tree = new int[MaxN]();
arr = new int[MaxN]();
for (int i = 1; i < MaxN; i++) {
arr[i] = nums[i - 1];
}
}
~IndexTree() {
delete[] tree;
delete[] arr;
}
void add(int index, int d) {
//在数组index位置增加d,这里indxe从1开始
while (index < MaxN) {
tree[index] += d;
index += index & (~index + 1);
}
}
void update(int index, int d) {
//将数组index的位置更新成d,这里index从1开始
int addval = d - arr[index];
add(index, addval);
}
int query(int index) {
//这里只能查范围从1到index的,若想查特定范围,请在上游调用两次这个函数,自行相减
int ans = 0;
while (index > 0) {
ans += tree[index];
index -= index & (~index + 1);
}
return ans;
}
};