前言
提示:这里可以添加本文要记录的大概内容:
线段树是用来维护 区间信息 的数据结构。
线段树可以在 的O(logN)时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
提示:以下是本篇文章正文内容,下面案例可供参考
一、树的结点定义
class lineTree {
public:
int l,/*左孩子*/ r/*右孩子*/, lazy/*懒惰标记*/, sum/*区间和*/;
lineTree(): l(0), r(0), lazy(0), sum(0) {}
};
二、操作
1.建树
//根节点的下标为1
//更新结点区间操作,它的左孩子为2 * i,右孩子为2 * i + 1
inline void update(int i) {
tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum;
}
//边界了l,r均表示的是数组的下标
void unit(int i, int l, int r, const vector<int>& data) {
//当前区间左边界等于右边界,此时结点为叶子结点
tree[i].l = l, tree[i].r = r;
if (l == r) {
tree[i].sum = data[l];
return;
}
int mid = (l + r) >> 1;
//递归到左孩子,建立左子树,将左半部分区间建立
unit(i << 1, l, mid, data);
//递归到右孩子,建立右子树,将右半部分区间建立
unit((i << 1) | 1, mid + 1, r, data);
update(i);//因为左右孩子代表的区间和已经更新,所以他们的父节点区间和也要更新
return;
}
2.单点修改
代码如下:
void singleFix(int k, int val, int i) {
//当前点为所要修改的点
if (tree[i].l == tree[i].r && tree[i].l == k) {
tree[i].sum += val;
return;
}
int mid = (tree[i].l + tree[i].r) >> 1;
//点k在左区间
if (mid >= k) singleFix(k, val, i << 1);
//点k在右区间
else singleFix(k, val, (i << 1) | 1);
update(i);
return;
}
3.区间修改
代码如下(区间加减):
void fixScope(int i, int l, int r, int x) {
//当前结点表示区间为所要修改的区间
pushDown(i);
if (tree[i].l == l && tree[i].r == r) {
tree[i].sum += (r - l + 1) * x;//当前区间和加上元素个数乘以x
tree[i].lazy += x; // 不用一个一个更新每个结点区间和,懒惰标记,后续查找利用此值修改
pushDown(i);
return;
}
int mid = (tree[i].l + tree[i].r) >> 1;
//被修改区间全部被包含与左孩子结点表示的区间中
if (mid >= r && tree[i].l <= l) fixScope(i << 1, l, r, x);
//被修改区间全部被包含与右孩子结点表示的区间中
else if (mid < l && tree[i].r >= r) fixScope((i << 1) | 1, l, r, x);
//被修改区间分别分散与左右孩子区间中,即一部分在当前结点区间左边, 一部分在右边
else {
fixScope(i << 1, l, mid, x);
fixScope((i << 1) | 1, mid + 1, r, x);
}
update(i);//记住这个!!!
return;
}
4.查询
int query(int l, int r, int i) {
//如果当前结点表示的区间为所要查询的区间,返回区间值并加上懒惰标记的内容,在下传标记
if (tree[i].l == l && tree[i].r == r) return tree[i].sum;
pushDown(i);
int sum = 0;
int mid = (tree[i].l + tree[i].r) >> 1;
//如果当前结点左孩子表示的区间包括查询区间,查询左孩子
if (mid >= r) return query(l, r, 2 * i);
//如果当前结点右孩子表示的区间包括查询区间,查询右孩子
else if (mid < l) return query(mid + 1, r, 2 * i + 1);
//查询区间分别分布在两边
else {
return query(l, mid, 2 * i) + query(mid + 1, r, 2 * i + 1);
}
}
4.标记下传(区间加)
void pushDown(int i) {
if (!tree[i].lazy) return;
if (tree[i].l == tree[i].r) {
tree[i].lazy = 0;
return;
}
tree[(i << 1) | 1].sum += (tree[(i << 1) | 1].r - tree[(i << 1) | 1].l + 1) * tree[i].lazy;
tree[i << 1].lazy = tree[i].lazy;
tree[(i << 1) | 1].lazy = tree[i].lazy;
tree[i].lazy = 0;
return;
}
三、整体代码(区间加)
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
class lineTree {
public:
int l, r, lazy, sum;
lineTree(): l(0), r(0), lazy(0), sum(0) {}
};
lineTree tree[N];
//下传标记:先看后面的操作这个后面看
void pushDown(int i) {
if (!tree[i].lazy) return;
if (tree[i].l == tree[i].r) {
tree[i].lazy = 0;
return;
}
tree[(i << 1) | 1].sum += (tree[(i << 1) | 1].r - tree[(i << 1) | 1].l + 1) * tree[i].lazy;
tree[i << 1].lazy = tree[i].lazy;
tree[(i << 1) | 1].lazy = tree[i].lazy;
tree[i].lazy = 0;
return;
}
//更新结点区间操作,它的左孩子为2 * i,右孩子为2 * i + 1, 位运算优化
inline void update(int i) {
tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum;
}
//建树
void unit(int i, int l, int r, const vector<int>& data) {
//当前区间左边界等于右边界,此时结点为叶子结点
tree[i].l = l, tree[i].r = r;
if (l == r) {
tree[i].sum = data[l];
return;
}
int mid = (l + r) >> 1;
//递归到左孩子,建立左子树,将左半部分区间建立
unit(i << 1, l, mid, data);
//递归到右孩子,建立右子树,将右半部分区间建立
unit((i << 1) | 1, mid + 1, r, data);
update(i);//记得要用左右子区间的值更新该区间的值
return;
}
//单点修改
void singleFix(int k, int val, int i) {
//当前点为所要修改的点
if (tree[i].l == tree[i].r && tree[i].l == k) {
tree[i].sum += val;
return;
}
int mid = (tree[i].l + tree[i].r) >> 1;
//点k在左区间
if (mid >= k) singleFix(k, val, i << 1);
//点k在右区间
else singleFix(k, val, (i << 1) | 1);
update(i);
return;
}
//区间修改:区间所有元素加上x的代码(相对标记)
void fixScope(int i, int l, int r, int x) {
//当前结点表示区间为所要修改的区间
pushDown(i);
if (tree[i].l == l && tree[i].r == r) {
tree[i].sum += (r - l + 1) * x;//当前区间和加上元素个数乘以x
tree[i].lazy += x; // 不用一个一个更新每个结点区间和,懒惰标记,后续查找利用此值修改
pushDown(i);
return;
}
tree[i].sum += (r - l + 1) * x;
int mid = (tree[i].l + tree[i].r) >> 1;
//被修改区间全部被包含与左孩子结点表示的区间中
if (mid >= r && tree[i].l <= l) fixScope(i << 1, l, r, x);
//被修改区间全部被包含与右孩子结点表示的区间中
else if (mid < l && tree[i].r >= r) fixScope((i << 1) | 1, l, r, x);
//被修改区间分别分散与左右孩子区间中,即一部分在当前结点区间左边, 一部分在右边
else {
fixScope(i << 1, l, mid, x);
fixScope((i << 1) | 1, mid + 1, r, x);
}
update(i);
return;
}
//查询区间和(区间修改为加上x),查询其他例如最大值再修改
int query(int l, int r, int i) {
if (tree[i].l == l && tree[i].r == r) return tree[i].sum;
pushDown(i);
int sum = 0;
int mid = (tree[i].l + tree[i].r) >> 1;
if (mid >= r) return query(l, r, 2 * i);
else if (mid < l) return query(mid + 1, r, 2 * i + 1);
else {
return query(l, mid, 2 * i) + query(mid + 1, r, 2 * i + 1);
}
}
int main() {
int n;
cin >> n;
vector<int> a;
for (int i = 0; i < n; i++) {
int data;
cin >> data;
a.push_back(data);
}
unit(1, 0, n - 1, a);
cout << "未修改前1到4的区间和为" << query(1, 4, 1) << endl;
singleFix(3, 4, 1);
cout << "单点修改后1到4的区间和为(a[3]+4)" << query(1, 4, 1) << endl;
fixScope(1, 2, n - 1, 5);
cout << "将下标2到" << n - 1 <<"元素全加上5的全部区间和为" << query(0, n - 1, 1) << endl;
fixScope(1, 0, 3, 5);
cout << "将0到3元素加上5的区间和(0到4)为" << query(0, 4, 1) << endl;
return 0;
}
动态开点线段树
const int N = 3500000;
class segmentTree{
int num[N] = {0}, lc[N] = {0}, rc[N] = {0}, cnt = 2, lazy0[N] = {0}, lazy1[N] = {0};
public:
void pushDown(int p) {
if(!lc[p]) lc[p] = cnt++;
if(!rc[p]) rc[p] = cnt++;
if(lazy0[p]) {
lazy0[lc[p]] = 1;
lazy1[lc[p]] = 0;
lazy0[rc[p]] = 1;
lazy1[rc[p]] = 0;
num[lc[p]] = 0;
num[rc[p]] = 0;
lazy0[p] = 0;
}
else if(lazy1[p]) {
lazy0[lc[p]] = 0;
lazy1[lc[p]] = 1;
lazy0[rc[p]] = 0;
lazy1[rc[p]] = 1;
num[lc[p]] = 1;
num[rc[p]] = 1;
lazy1[p] = 0;
}
}
void update(int p, int l1, int r1, int L, int R, int val) {
if(L > r1 || R < l1) return;
if(L <= l1 && R >= r1) {
num[p] = val;
if(val) {
lazy0[p] = 0;
lazy1[p] = 1;
num[p] = 1;
}else {
lazy0[p] = 1;
lazy1[p] = 0;
num[p] = 0;
}
return;
}
pushDown(p);
int mid = (l1 + r1) >> 1;
update(lc[p], l1, mid, L, R, val);
update(rc[p], mid + 1, r1, L, R, val);
num[p] = num[lc[p]] && num[rc[p]];
}
bool query(int p, int l1, int r1, int L, int R) {
if(L > r1 || R < l1) return true;
if(L <= l1 && R >= r1) {
return num[p];
}
pushDown(p);
int mid = (l1 + r1) >> 1;
bool ans = true;
ans &= query(lc[p], l1, mid, L, R);
ans &= query(rc[p], mid + 1, r1, L, R);
return ans;
}
};
总结
理解清线段树结点表示的含义(表示的区间的两个边界,比如,根节点tree[1]表示数组下标从0到n-1整个数组的和,左右孩子分别代表父节点区间的两半),根据实际修改一下懒标记的内容,在查询和修改和下传标记中修改一下有关使用懒标记的操作。比如将整个区间加上某个值,在修改和查询时就需要加所修改区间长度乘以这个值,然后下传标记;如果将区间全部修改为某个值那这个+=就得变成=。
例题
区间修改某个值的例题:
leetcode LCP52二叉搜索树染色: