适用场景:
- 需要开一棵空间非常大的线段树
- 数组下标需要以负数为索引
具体实现:
- 我们是一边修改一边动态建立线段树,所以不需要build
- 获取左右儿子不再是p * 2 和 p * 2 + 1,而是用两个数组 lson[p] 和 rson[p] 存两个儿子所在位置
- 与lazy标记有同样的思想,需要访问儿子时,再动态地为它分配位置,所以大多数儿子都是以虚拟的方式存在,这样就避免了空间浪费
优点:
- 解决了空间限制的问题
- 无视索引下标的限制,用一个数组元素表示一段区间的值。
例如,求数组下标 -100 到 0 所对应元素的和,query(1, -100, 100, -100, 0),此时 t[1] 表示的是sum(nums[-100:100]),而它的左儿子 t[lson[1]] 表示的就是sum(nums[-100:0]),即所求答案
题目链接:850. 矩形面积 II
这题属实经典
- 用动态开点线段树维护扫描线,根节点的值tr[1]就是扫描线被覆盖的总长度
- 维护一个tag标记,tag[i] == 1,表示这条线段被完全覆盖,此时当前线段长度tr[p] = r - l + 1,否则就是没有被完全覆盖,tr[p] = tr[lson[p]] + tr[rson[p]]
- 在整个过程中不需要懒标记传递
class Solution {
typedef long long ll;
ll tr[3500000], tag[3500000], lson[1500000], rson[1500000], cnt = 2, mod = 1e9 + 7;
public:
void change(ll p, ll l, ll r, ll s, ll t, ll v) {
if (r < s || l > t) {
return;
} else if (s <= l && r <= t) {
tag[p] += v;
if (tag[p]) tr[p] = r - l + 1;
else tr[p] = (tr[lson[p]] + tr[rson[p]]) % mod;
} else {
ll m = l + r >> 1;
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
change(lson[p], l, m, s, t, v);
change(rson[p], m + 1, r, s, t, v);
if (tag[p]) tr[p] = r - l + 1;
else tr[p] = (tr[lson[p]] + tr[rson[p]]) % mod;
}
}
int rectangleArea(vector<vector<int>>& r) {
ll res = 0;
vector<vector<int>> edges;
for (int i = 0; i < r.size(); i++) {
edges.push_back({r[i][0], r[i][1] + 1, r[i][3], 1});
edges.push_back({r[i][2], r[i][1] + 1, r[i][3], -1});
}
sort(edges.begin(), edges.end(), [](vector<int>& a, vector<int>& b) {
return a[0] < b[0];
});
ll last = edges[0][0];
for (int i = 0; i < edges.size(); i++) {
res = (res + (edges[i][0] - last) * tr[1]) % mod;
last = edges[i][0];
change(1, 0, 1e9, edges[i][1], edges[i][2], edges[i][3]);
}
return res;
}
};
题目链接:220. 存在重复元素 III
本题的官方解法是滑动窗口+有序集合或桶,翻了翻题解,好像我是第一个用动态开点线段树来解题的
class Solution {
typedef long long ll;
ll tr[1500000], lson[1500000], rson[1500000], cnt = 2;
public:
void change(ll p, ll l, ll r, ll x, ll v) {
if (r < x || l > x) {
return;
} else if (l == r && l == x) {
tr[p] = v;
return;
} else {
ll m = l + r >> 1;
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
change(lson[p], l, m, x, v);
change(rson[p], m + 1, r, x, v);
tr[p] = tr[lson[p]] + tr[rson[p]];
}
}
ll query(ll p, ll l, ll r, ll s, ll t) {
if (r < s || l > t) {
return 0;
} else if (s <= l && r <= t) {
return tr[p];
} else {
ll m = l + r >> 1;
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
return query(lson[p], l, m, s, t) + query(rson[p], m + 1, r, s, t);
}
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int d) {
cnt = 2;
ll n = nums.size();
for (ll i = 0; i < n; i++) {
if (i > k) {
change(1, -3e9, 3e9, nums[i - k - 1], 0);
}
if (query(1, -3e9, 3e9, (ll)nums[i] - d, (ll)nums[i] + d)) {
return true;
}
change(1, -3e9, 3e9, nums[i], 1);
}
return false;
}
};
题目链接:2070. 每一个查询的最大美丽值
线段树维护区间最大值
class Solution {
int tr[3500000], lson[3500000], rson[3500000], cnt = 2;
public:
void change(int p, int l, int r, int x, int v) {
if (r < x || l > x) {
return;
} else if (l == r && l == x) {
tr[p] = max(tr[p], v);
return;
} else {
int m = l + r >> 1;
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
change(lson[p], l, m, x, v);
change(rson[p], m + 1, r, x, v);
tr[p] = max(tr[lson[p]], tr[rson[p]]);
}
}
int query(int p, int l, int r, int s, int t) {
if (r < s || l > t) {
return 0;
} else if (s <= l && r <= t) {
return tr[p];
} else {
int m = l + r >> 1;
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
return max(query(lson[p], l, m, s, t), query(rson[p], m + 1, r, s, t));
}
}
vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {
int n = items.size();
for (int i = 0; i < n; i++) {
change(1, 1, 1e9, items[i][0], items[i][1]);
}
vector<int> res;
for (int x : queries) {
res.push_back(query(1, 1, 1e9, 1, x));
}
return res;
}
};
题目链接:715. Range 模块
题意大概为给定一个区间,把给定区间全部设为true,或全部设为false,或询问给定区间是否全部为true
- 看到区间操作,自然想到线段树
- 数据范围1e9,采用动态开点来避免多余的空间消耗
- 同时维护两个懒标记,lazy0和lazy1,lazy0[i] == 1表示该区间元素全部为0,lazy1[i] == 1表示该区间元素全部为1
- 注意每次添加一个新标记时,之前的标记要全部作废
class RangeModule {
int tr[3500000], lson[3500000], rson[3500000], cnt = 2, lazy0[3500000], lazy1[3500000];
public:
RangeModule() {
}
void push_down(int p) {
if (lson[p] == 0) lson[p] = cnt++;
if (rson[p] == 0) rson[p] = cnt++;
if (lazy0[p]) {
lazy0[lson[p]] = 1;
lazy0[rson[p]] = 1;
lazy1[lson[p]] = 0;
lazy1[rson[p]] = 0;
tr[lson[p]] = 0;
tr[rson[p]] = 0;
lazy0[p] = 0;
}
if (lazy1[p]) {
lazy0[lson[p]] = 0;
lazy0[rson[p]] = 0;
lazy1[lson[p]] = 1;
lazy1[rson[p]] = 1;
tr[lson[p]] = 1;
tr[rson[p]] = 1;
lazy1[p] = 0;
}
}
void change(int p, int l, int r, int s, int t, int v) {
if (r < s || l > t) {
return;
} else if (s <= l && r <= t) {
if (v == 0) {
lazy0[p] = 1;
lazy1[p] = 0;
tr[p] = 0;
} else {
lazy0[p] = 0;
lazy1[p] = 1;
tr[p] = 1;
}
} else {
int m = l + r >> 1;
push_down(p);
change(lson[p], l, m, s, t, v);
change(rson[p], m + 1, r, s, t, v);
tr[p] = tr[lson[p]] & tr[rson[p]];
}
}
int query(int p, int l, int r, int s, int t) {
if (r < s || l > t) {
return 1;
} else if (s <= l && r <= t) {
return tr[p];
} else {
int m = l + r >> 1;
push_down(p);
return query(lson[p], l, m, s, t) & query(rson[p], m + 1, r, s, t);
}
}
void addRange(int left, int right) {
change(1, 0, 1e9, left, right - 1, 1);
}
bool queryRange(int left, int right) {
return query(1, 0, 1e9, left, right - 1);
}
void removeRange(int left, int right) {
change(1, 0, 1e9, left, right - 1, 0);
}
};