吉司机线段树(GSS)是一种基于线段树数据结构的算法,用于解决区间查询问题。它被广泛应用于解决数组的区间最大子段和、区间最小子段和以及区间和等问题。
线段树是一种二叉树,每个节点表示一个区间。根节点表示整个数组的区间,而每个叶子节点表示数组的一个单元。每个节点维护一个区间的信息,如区间和、区间最大值、区间最小值等。
吉司机线段树的特点是通过节点间的合并操作来维护区间信息。具体来说,每个节点的信息是通过合并其左子节点和右子节点的信息得到的。例如,对于求区间和的问题,每个节点的信息就是其左子节点的和加上右子节点的和。
为了更高效地进行区间查询,吉司机线段树还引入了一些优化措施,如延迟更新和懒惰标记。延迟更新可以在需要时才更新节点的信息,而懒惰标记可以将更新操作推迟到需要时才进行。
吉司机线段树的构建时间复杂度为O(nlogn),其中n是数组的长度。区间查询的时间复杂度为O(logn)。由于吉司机线段树的性能优秀,它被广泛应用于解决各种区间查询问题,如最大子段和、最小子段和、区间和等。
以下粘上代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int INF = 1e9; // 定义一个无穷大的数
struct Node {
int mx1, mx2, cnt_mx1; // 最大值,次大值,最大值的个数
int lazy_tag; // 懒惰标记
Node() : mx1(-INF), mx2(-INF), cnt_mx1(0), lazy_tag(0) {}
};
class SegmentTree {
private:
vector<Node> tree;
vector<int> arr;
void push_up(int idx) {
tree[idx].mx1 = max(tree[idx * 2].mx1, tree[idx * 2 + 1].mx1);
tree[idx].mx2 = max(min(tree[idx * 2].mx1, tree[idx * 2 + 1].mx1),
max(tree[idx * 2].mx2, tree[idx * 2 + 1].mx2));
tree[idx].cnt_mx1 = 0;
if (tree[idx * 2].mx1 == tree[idx].mx1) tree[idx].cnt_mx1 += tree[idx * 2].cnt_mx1;
if (tree[idx * 2 + 1].mx1 == tree[idx].mx1) tree[idx].cnt_mx1 += tree[idx * 2 + 1].cnt_mx1;
}
void push_down(int idx) {
if (tree[idx].lazy_tag != 0) {
tree[idx * 2].mx1 += tree[idx].lazy_tag;
tree[idx * 2 + 1].mx1 += tree[idx].lazy_tag;
tree[idx * 2].lazy_tag += tree[idx].lazy_tag;
tree[idx * 2 + 1].lazy_tag += tree[idx].lazy_tag;
tree[idx].lazy_tag = 0;
}
}
public:
SegmentTree(const vector<int>& nums) {
int n = nums.size();
arr = nums;
tree.resize(n * 4);
build(1, 0, n - 1);
}
void build(int idx, int l, int r) {
if (l == r) {
tree[idx].mx1 = arr[l];
tree[idx].cnt_mx1 = 1;
return;
}
int mid = (l + r) / 2;
build(idx * 2, l, mid);
build(idx * 2 + 1, mid + 1, r);
push_up(idx);
}
void update(int idx, int l, int r, int L, int R, int val) {
if (L <= l && r <= R) {
if (val >= tree[idx].mx1) return;
if (val > tree[idx].mx2) {
tree[idx].mx1 = val;
tree[idx].lazy_tag = val;
return;
}
}
push_down(idx);
int mid = (l + r) / 2;
if (L <= mid) update(idx * 2, l, mid, L, R, val);
if (R > mid) update(idx * 2 + 1, mid + 1, r, L, R, val);
push_up(idx);
}
int query(int idx, int l, int r, int L, int R) {
if (L <= l && r <= R) return tree[idx].mx1;
push_down(idx);
int ans = -INF;
int mid = (l + r) / 2;
if (L <= mid) ans = max(ans, query(idx * 2, l, mid, L, R));
if (R > mid) ans = max(ans, query(idx * 2 + 1, mid + 1, r, L, R));
return ans;
}
};
int main() {
vector<int> nums = {3, 2, 4, 5, 1, 2}
SegmentTree st(nums);
cout << st.query(1, 0, nums.size() - 1, 0, 3) << endl; // 查询区间[0, 3]的最大值
st.update(1, 0, nums.size() - 1, 0, 2, );
return 0;
}
在讨论线段树的关键操作之前,我们先来了解一下线段树的基本概念和性质。线段树是一种用于解决区间查询问题的数据结构,它将一个区间划分为多个子区间,并对每个子区间维护一些有用的信息,例如最大值、最小值、和等等。线段树的构建和查询操作都可以在对数时间复杂度内完成。需要注意以下几点:
-
线段树的构建:构建线段树时,需要考虑边界条件。通常情况下,线段树的叶子节点对应于原始输入数组的元素,因此线段树的构建过程涉及到对叶子节点的赋值。同时,我们还需要确定线段树的递归终止条件,即当区间缩小到只包含一个元素时,不再继续划分区间。
-
线段树的查询操作:线段树的查询操作主要是通过搜索适当的区间来获得所需的信息。在进行查询时,需要考虑到以下几个问题:a) 判断当前搜索的区间是否和目标区间有交集,如果没有交集,则可以直接返回空值;b) 如果当前搜索的区间完全包含在目标区间内,那么可以直接返回当前区间的信息;c) 如果当前搜索的区间和目标区间部分重叠,那么需要对当前区间进行进一步划分并递归搜索。
-
线段树的更新操作:线段树的更新操作主要包括对叶子节点的更新以及对父节点的更新。当某个叶子节点的值发生变化时,我们需要更新对应的父节点的信息。这可以通过递归地向上更新实现。
-
线段树的空间复杂度:线段树的空间复杂度取决于构建的树的高度。在一般情况下,线段树的空间复杂度为O(NlogN),其中N是原始输入的大小。但是,通过使用压缩技术(如吉司机线段树)可以将空间复杂度降低到O(N)。
在实际应用中,还有许多其他的细节需要注意,如线段树的边界条件处理、懒惰更新、区间合并等。因此,在使用吉司机线段树时,我们需要仔细考虑这些细节,以确保正确性和效率。