五月集训(第24天)—线段树
线段树
破防了,线段树好难,啊不对,是线段树和扫描线放一起就有些不理解了,边抄边理解,理解了个大概,以后再深入一下
线段树模板
先来个线段树模板
// 建树
void build(int s, int t, int p) {
if (s == t) {
d[p] = a[s];
return ;
}
int mid = s + ((t - s) >> 1);
build(s, mid, p * 2);
build(mid + 1, t, p * 2 + 1);
d[p] = d[p * 2] + d[p * 2 + 1];
}
// 区间和
int get_sum(int l, int r, int s, int t, int p) { /* [l, r]为查询区间 */
if (l <= s && t <= r) return d[p];
int mid = s + ((t - s) >> 1);
int sum = 0;
if (l <= mid) sum += get_sum(l, r, s, mid, p * 2);
if (r > mid) sum += get_sum(l, r, mid + 1, t, p * 2 + 1);
return sum;
}
// 区间增加一个值
void update(int l, int r, int s, int t, int p, int c) { /* c是区间增量 */
if (l <= s && t <= r) { // 如果当前区间为增加区间的子区间,则直接对d[]更新,并打上懒人标价
/* 到达一个不需要在分的区间,更新d[] */
d[p] += c * (t - s + 1);
b[p] += c;
return ;
}
int mid = s + ((t - s) >> 1);
if (b[p] && s != t) { /* 如果查询的区间有懒人标记,则将标记下放到子区间 */
// 到达一个需要再分的区间时,当前区间的d[]已经更新过了,为了保持一致性,将其分出的子区间的d[]也更新了
d[p * 2] += b[p] * (mid - s + 1), d[p * 2 + 1] += b[p] * (t - mid); // 注意区间长度的计算,mid包含在左半区间
b[p * 2] += b[p], b[p * 2 + 1] += b[p];
b[p] = 0;
}
if (l <= mid) update(l, r, s, mid, p * 2, c);
if (r > mid) update(l, r, mid + 1, t, p * 2 + 1, c);
d[p] = d[p * 2] + d[p * 2 + 1]; // 子区间更新完了,直接利用子区间更新当前区间
}
// 带懒人标记处理的区间和查询
int get_sum_lazy(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) return d[p];
int mid = s + ((t - s) >> 1);
if (b[p]) {
/* 每个区间自己的懒人标记已经更新过 d[] 了,即对于子区间的修改也只需要将当前区间的b[]加给子区间的d[] */
d[p * 2] += b[p] * (mid - s + 1), dp[p * 2 + 1] += b[p] * (t - mid);
b[p * 2] += b[p], b[p * 2 + 1] += b[p];
b[p] = 0;
}
int sum = 0;
if (l <= mid) sum += get_sum_lazy(l, r, s, mid, p * 2);
if (r > mid) sum += get_sum_lazy(l ,r, mid + 1, t, p * 2 + 1);
return sum;
}
/** 区间修改为某一值 **/
void update_change(int l, int r, int s, int t, int p, int c) { /* 为区间[l, r]内的值将被修改为c */
if (l <= s && t <= r) {
d[p] = c * (t - s + 1);
b[p] = c;
return ;
}
int mid = s + ((t - s) >> 1);
if (v[p]) {
d[p * 2] = d[p] * (mid - s + 1), d[p * 2 + 1] = d[p] * (t - mid);
b[p * 2] = b[p], b[p * 2 + 1] = b[p];
v[p * 2] = v[p * 2 + 1] = 1; // 打上区间修改的懒人标记
v[p] = 0;
}
if (l <= mid) update_change(l, r, s, mid, p * 2, c);
if (mid < r) update_change(l, r, mid + 1, t, p * 2 + 1, c);
d[p] = d[p * 2] + d[p * 2 + 1];
}
// 区间修改为某一值后的区间和查询
int get_sum_v(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) return d[p];
int mid = s + ((t - s) >> 1);
if (v[p]) {
d[p * 2] = d[p] * (mid - s + 1), d[p * 2 + 1] = d[p] * (t -mid);
b[p * 2] = b[p], b[p * 2 + 1] = b[p];
v[p * 2] = v[p * 2 + 1] = 1;
v[p] = 0;
}
int sum = 0;
if (l <= mid) sum += get_sum_v(l, r, s, mid, p * 2);
if (r > mid) sum += get_sum_v(l, r, mid + 1, t, p * 2 + 1);
return sum;
}
1. 850. 矩形面积 II
思路:
离散化 + 扫描线 + 线段树
class Solution {
/*** 1.定义垂直线段 ***/
#define mod 1000000007
struct VSeg {
int x; // 垂直线段的 x 坐标
int y1, y2; // 垂直线段的两个 y 坐标
int v; // +1(入边) 或者 -1(出边)
VSeg() {}
VSeg(int _x, int _y1, int _y2, int _v): x(_x), y1(_y1), y2(_y2), v(_v) {}
};
vector<VSeg> vs;
vector<int> Y; // 存储离散化结果
/*** 2.定义离散化相关的函数 ***/
// 通过下标找值
int get_value(int idx) {
return Y[idx];
}
// 通过值找离散化的下标
int get_index(int val) {
int l = 0, r = Y.size() - 1;
while (l <= r) {
int mid = l + ((r - l) >> 1);
if (Y[mid] == val) return mid;
else if (val > Y[mid]) l = mid + 1;
else r = mid - 1;
}
return -1;
}
/*** 3.定义线段树结点,其中len代表当前结点管辖的区间内完全覆盖的线段的长度总和,cover是线段完全覆盖区间的次数 ***/
struct segNode {
segNode *left, *right;
int len;
int cover;
segNode() { // 初始化
left = right = NULL;
len = 0;
cover = 0;
}
};
/*** 4.递归构建线段树 ***/
segNode* segtree_build(int l, int r) {
if (l >= r) return NULL;
segNode *root = new segNode();
if (l + 1 == r) return root; /* 若l==r则为点(没有面积),所以最小的结点区间长为1 */
int mid = l + ((r - l) >> 1);
root->left = segtree_build(l, mid);
root->right = segtree_build(mid, r);
return root;
}
/*** 5.插入线段过程 ***/
// 两个线段是否相交
bool is_not_intersect(int l, int r, int s, int t) {
return r <= s || t <= l; // 不相交
}
// 返回前一个线段是否完全包含后一个线段
bool is_contain(int l, int r, int s, int t){
return l <= s && t <= r;
}
// 更新当前树的len
void update_root(segNode *root, int l, int r) {
if (root->cover > 0) root->len = get_value(r) - get_value(l);
else {
if (l + 1 == r) root->len = 0;
else merge_from_son(root);
}
}
// 根据子节点计算当前树的len
void merge_from_son(segNode *root) {
root->len = root->left->len + root->right->len;
}
// 线段树的插入操作
void segtree_insert(segNode *root, int l, int r, int s, int t, int val) {
if (is_not_intersect(l, r, s, t)) return ;
if (is_contain(l, r, s, t)) {
root->cover += val;
update_root(root, s, t);
return ;
}
int mid = s + ((t - s) >> 1);
segtree_insert(root->left, l, r, s, mid, val);
segtree_insert(root->right, l, r, mid, t, val);
update_root(root, s, t);
}
public:
int rectangleArea(vector<vector<int>>& rectangles) {
int i;
vs.clear();
Y.clear();
// step1:将所有矩形用出边和入边表示,也就是两条垂直于x轴的线段
int rect_size = rectangles.size();
for (i = 0; i < rect_size; ++i) {
vs.push_back( VSeg(rectangles[i][0], rectangles[i][1], rectangles[i][3], +1) );
vs.push_back( VSeg(rectangles[i][2], rectangles[i][1], rectangles[i][3], -1) );
Y.push_back( rectangles[i][1] );
Y.push_back( rectangles[i][3] );
}
// step2: 将线段按照 x 坐标排序
sort(vs.begin(), vs.end(), [&](const VSeg &a, const VSeg &b) {return a.x < b.x;} );
sort(Y.begin(), Y.end());
// step3: Y坐标进行离散化
Y.erase( unique(Y.begin(), Y.end()), Y.end() );
// step4: m代表了线段树的最大区间为 [0, m-1],也可以认为是 [0, m]
int m = Y.size() - 1;
long long ans = 0;
segNode *root = segtree_build(0, m);
int vs_size = vs.size();
for (i = 0; i < vs_size; ++i) {
int l = vs[i].y1;
int r = vs[i].y2;
int val = vs[i].v;
if (i) {
ans += (long long)root->len * (vs[i].x - vs[i - 1].x);
ans %= mod;
}
segtree_insert(root, get_index(l), get_index(r), 0, m, val);
}
return ans;
}
};
东方欲晓,莫道君行早。