线段树题目总结

news/2024/4/26 7:39:12/文章来源:https://blog.csdn.net/fuzekun/article/details/129265479

参考文档

线段树动态开点java
动态开点c++
线段树leetcode题目
线段树codeforce专题

题目

题目知识点/注意点难度
更新数组后处理求和查询push_down应该是子区间的长度困难
区间最大公约数数论 +注意l, r困难
子序列和的最大值询问过程中,需要push_up困难
维护序列区间修改、求和困难
699.掉落的方块区间修改,求最大值困难
4. 气球颜色变换右边区间-1
218.天际线右边区间-1困难
307. 区域和检索 - 数组可修改下标开始的位置困难
315. 计算右侧小于当前元素的个数保证r >= l困难
493. 翻转对离散化 / 动态开点困难
327. 区间和的个数离散化 / 动态开点困难
range模块注意push_down设计困难
我的日程安排差分/线段树困难
矩形面积2天际线的进化版
题解困难
亚特兰蒂斯就是矩形的面积2

线段树实现

  1. 一般Push_down和push_up是需要手动实现的。
  2. 注意下标从1开始,build和使用的时候需要注意。
  3. 动态开点的时候,push_down会动态开点,32位开4e6。
  4. 使用的时候,需要保证r >= l,否则会出现各种问题。
  5. 动态开点的时候,需要保证(l, r)的范围包含全部点。
  6. 动态开点的时候,注意long long的使用。
  7. 动态开点的时候,idx = root = 1,那么可以直接使用push_down更新。应用于区间
  8. idx = root = 0,那么应该判断if (!u) u = ++idx; 应用于单点。加上&
  9. 不要把l, r放在Node中,要不然Build的时候,每次需要更改。

单点修改,区间求值

模板题目

    static const int maxn = 4e5 + 4;int sum[maxn];int n;void push_up(int u) {sum[u] = sum[u << 1] + sum[u << 1 | 1];}void build(int u, int l, int r, vector<int>&nums) {if (l == r) {sum[u] = nums[l - 1];return ;}int mid = (l + r) >> 1;build(u << 1, l, mid, nums);build(u << 1 | 1, mid + 1, r, nums);push_up(u);}void update(int u, int l, int r, int p, int x) {if (p == l && l == r) {sum[u] = x;return ;}int mid = (l + r) >> 1;if (p <= mid) update(u << 1, l, mid, p, x);else update(u << 1 | 1, mid + 1, r, p, x);push_up(u);}int query(int u, int l, int r, int L, int R) {if (L <= l && r <= R) {return sum[u];}int mid = (l + r) >> 1;int ans = 0;if (L <= mid) ans = query(u << 1, l, mid, L, R);if (R > mid) ans += query(u << 1 | 1, mid + 1, r, L, R);return ans; }

308. 二维区域和检索 - 可变

class Solution {
public:// 加是不行的。为什么呢?因为区间[l, r]上的高度不统一。static const int N = 1e6 + 10;int maxh[N], lc[N], rc[N], idx = 0, root = 0; //  root应该写成全局变量,int lazy[N];// int add[N];         // 区间同时增加高度不行void push_up(int u) {maxh[u] = max(maxh[lc[u]], maxh[rc[u]]);}// 将区间,改编成add_h。如果没有区间就开点void push_down(int &u, int x) {if (!u) u = ++idx;                // push_down的时候也需要创建新的lazy[u] = maxh[u] = x;}// 懒标记下推void push_down(int u) {// if (add[u] == 0) return ;if (lazy[u] == 0) return ;push_down(lc[u], lazy[u]);push_down(rc[u], lazy[u]);lazy[u] = 0;                     // 清除懒标记}// [L, R]增加add_x, 需要加上引用,让lc和rc指向正确位置。void update(int &u, int l, int r, int L, int R, int add_x) {if (u == 0) u = ++idx;           // 给儿子创建一个新的点if (L <= l && r <= R) {push_down(u, add_x);// printf("add_x = %d\n", add_x);return ;}push_down(u);int mid = (l + r) >> 1;// printf("mid = %d l = %d r = %d, L = %d, R = %d\n", mid, l, r, L, R);if (L <= mid) update(lc[u], l, mid, L, R, add_x);if (R > mid) update(rc[u], mid + 1, r, L, R, add_x);push_up(u);                     // 没有传递u, 所以u不会变}// 询问,不用加引用int query(int u, int l, int r, int L, int R) {if (u == 0) return 0;           // 如果没更新这个区间直接返回if (L <= l && r <= R) {return maxh[u];}push_down(u);int mid = (l + r) >> 1;int maxv = 0;// printf("%d %d %d %d %d\n", mid, l, r, L, R);if (L <= mid) maxv = query(lc[u], l, mid, L, R);if (R > mid) maxv = max(maxv, query(rc[u], mid + 1, r, L, R));return maxv;}vector<int> fallingSquares(vector<vector<int>>& positions) {vector<int>ans;for (auto vec : positions) {int l = vec[0], r = vec[0] + vec[1] - 1, z = vec[1];int cur = query(root, 1, 1e9, l, r);// printf("cur = %d\n", cur);update(root, 1, 1e9, l, r, z + cur);ans.push_back(maxh[1]);}return ans;}
};

区间修改,区间求值

1. 掉落的方块(区间开点)

大区间

少查询

动态开点: 使用指针的进行开点

离散化

int x = info[0], h = info[1], cur = query(1, 1, N, x, x + h - 1);

查询 – [x, x + h - 1]

修改 – [x, x + h - 1, cur + h]

当前的最大值就是tr[1].val

class Solution {
public:// 加是不行的。为什么呢?因为区间[l, r]上的高度不统一。static const int N = 1e6 + 10;int maxh[N], lc[N], rc[N], idx = 0, root = 0; //  root应该写成全局变量,int lazy[N];// int add[N];         // 区间同时增加高度不行void push_up(int u) {maxh[u] = max(maxh[lc[u]], maxh[rc[u]]);}// 将区间,改编成add_h。如果没有区间就开点void push_down(int &u, int x) {if (!u) u = ++idx;                // push_down的时候也需要创建新的lazy[u] = maxh[u] = x;}// 懒标记下推void push_down(int u) {// if (add[u] == 0) return ;if (lazy[u] == 0) return ;push_down(lc[u], lazy[u]);push_down(rc[u], lazy[u]);lazy[u] = 0;                     // 清除懒标记}// [L, R]增加add_xvoid update(int &u, int l, int r, int L, int R, int add_x) {if (u == 0) u = ++idx;           // 给儿子创建一个新的点if (L <= l && r <= R) {push_down(u, add_x);// printf("add_x = %d\n", add_x);return ;}push_down(u);int mid = (l + r) >> 1;// printf("mid = %d l = %d r = %d, L = %d, R = %d\n", mid, l, r, L, R);if (L <= mid) update(lc[u], l, mid, L, R, add_x);if (R > mid) update(rc[u], mid + 1, r, L, R, add_x);push_up(u);                     // 没有传递u, 所以u不会变}int query(int &u, int l, int r, int L, int R) {if (u == 0) return 0;           // 如果没更新这个区间直接返回if (L <= l && r <= R) {return maxh[u];}push_down(u);int mid = (l + r) >> 1;int maxv = 0;// printf("%d %d %d %d %d\n", mid, l, r, L, R);if (L <= mid) maxv = query(lc[u], l, mid, L, R);if (R > mid) maxv = max(maxv, query(rc[u], mid + 1, r, L, R));return maxv;}vector<int> fallingSquares(vector<vector<int>>& positions) {vector<int>ans;for (auto vec : positions) {int l = vec[0], r = vec[0] + vec[1] - 1, z = vec[1];int cur = query(root, 1, 1e9, l, r);// printf("cur = %d\n", cur);update(root, 1, 1e9, l, r, z + cur);ans.push_back(maxh[1]);}return ans;}
};
  • 问题1:为什么动态开点不判断u是否为空?询问直接返回就行了,更新的时候需要判断。
  • 问题2:直接把val赋值成为add,是否都是这样?还是仅仅是这个题目这样?仅仅这个题目这样。

2. 维护序列

调试技巧

  1. 首先判断build是否成功
  2. 其次判断询问是否成功
  3. 最后根据根节点的信息判断更新是否成功。
  4. push_down和push_up可以输出前后的sum[u]进行判断

3. 一个简单的问题2

java代码再acwnig的java代码上。

  1. long的使用
  2. 两个push_down
    1. 一个push_down是下推
    2. 另一个是更新区间的sum和懒标记

4. 天际线问题

  1. 离散化
  2. 扫描线
  3. 右端点-1,左端点仍旧可以作为起点。

class NumMatrix {
public:// 每一行使用一个线段树来维护static const int MAXN = 8e4 + 4;static const int N = 3e2 + 4;int sum[N][MAXN];vector<vector<int>>nums;int m, n;void push_up(int row, int u) {sum[row][u] = sum[row][u << 1] + sum[row][u << 1 | 1];}void build(int row, int u, int l, int r) {if (l == r) {sum[row][u] = nums[row][l - 1];return ;}int mid = (l + r) >> 1;// printf("%d %d %d\n", l, r, mid);build(row, u << 1, l, mid);build(row, u << 1 | 1, mid + 1, r);push_up(row, u);}void update(int u, int l, int r, int row, int col, int x) {if (l == r && col == l) {       // l == r的时候col一定等于lsum[row][u] = x;return ;}int mid = (l + r) >> 1;if (col <= mid) update(u << 1, l, mid, row, col, x);else update(u << 1 | 1, mid + 1, r, row, col, x);push_up(row, u);}int query(int u, int l, int r, int row, int L, int R) {if (L <= l && r <= R) {return sum[row][u];}int mid = (l + r) >> 1;int ans = 0;if (L <= mid) ans += query(u << 1, l, mid, row, L, R);if (R > mid) ans += query(u << 1 | 1, mid + 1, r, row, L, R);return ans; }NumMatrix(vector<vector<int>>& matrix) {this->nums = matrix;m = nums.size();n = nums[0].size();for (int i = 0; i < m; i++) {build(i, 1, 1, n);// printf("%d\n", sum[i][1]);}}void update(int row, int col, int val) {col++;update(1, 1, n, row, col, val);}int sumRegion(int row1, int col1, int row2, int col2) {int ans = 0;col1++, col2++;for (int i = row1; i <= row2; i++) {ans += query(1, 1, n, i, col1, col2);}return ans;}
};/*** Your NumMatrix object will be instantiated and called as such:* NumMatrix* obj = new NumMatrix(matrix);* obj->update(row,col,val);* int param_2 = obj->sumRegion(row1,col1,row2,col2);*/

动态开点

1. 区间和个数(单点修改开点)

  • 注意求最大和最小值的过程
  • 注意前缀和为0的情况需要加入进去。而前缀和为0的时候,不用考虑query,因为一定为0,所有的点都是0。
  typedef long long LL;static const int N = 4e6 + 10;              // 开太小了也爆栈LL add[N];int idx = 0, root = 0, lc[N], rc[N];void push_up(int u) {add[u] = add[lc[u]] + add[rc[u]];       // 如果使用node,可能会出现点不存在的情况,但是这里并不会,因为add[0] = 0;}// 单点修改开点void update(int &u, LL l, LL r, LL p) {if (!u) u = ++idx;// add[u]++;                               // 直接相当于push_up了if (l == r && l == p) {add[u] += 1;return ;} LL mid = (l + r) >> 1;if (p <= mid) update(lc[u], l, mid, p);else update(rc[u], mid + 1, r, p);push_up(u);}LL query(int u, LL l, LL r, LL L, LL R) {if (!u) return 0;if (L <= l && r <= R) return add[u];LL mid = (l + r) >> 1;LL ans = 0;if (L <= mid) ans += query(lc[u], l, mid, L, R);if (R > mid) ans += query(rc[u], mid + 1, r, L, R);return ans;}int countRangeSum(vector<int>& nums, int low, int high) {int n = nums.size();LL ans = 0;vector<LL>sum(n + 1);sum[0] = 0;memset(lc, 0, sizeof lc);memset(rc, 0 ,sizeof rc);for (int i = 1; i <= n; i++) {sum[i] = sum[i - 1] + nums[i - 1];}LL minv = LLONG_MAX, maxv = LLONG_MIN;for (LL x: sum) {minv = min({minv, x, x - high});maxv = max({maxv, x, x - high});}// cout << minv << " " << maxv << endl;for (LL x: sum) {LL l = x - high, r = x - low;// cout <<x << " " << l << " " << r << endl;ans += query(root, minv, maxv, l, r);// cout << query(root, minv, maxv, l, r) << endl;update(root, minv, maxv, x);}return ans;}
};

2. range模块(区间开点)

class RangeModule {static final int maxn = (int)4e6 + 10;static final int MAXN = (int)1e9;boolean [] c, add;int[] lc, rc;int idx = 1, root = 1;public void push_up(int u) {c[u] = c[lc[u]] & c[rc[u]];}public void push_down(int u, boolean flag) {c[u] = flag;                       add[u] = true;                  // 不是取反。}public void push_down(int u) {if (lc[u] == 0) lc[u] = ++idx;  // 动态开点if (rc[u] == 0) rc[u] = ++idx;if (!add[u]) return ;push_down(lc[u], c[u]);         // 父亲区间当时是什么,他就是什么push_down(rc[u], c[u]);add[u] = false;}// update的时候动态开点,push_down的时候,以及每次访问的时候。public void update(int u, int l, int r, int L, int R, boolean flag) {if (L <= l && r <= R) {push_down(u, flag);return ;}push_down(u);int mid = (l + r) >> 1;if (L <= mid) update(lc[u], l, mid, L, R, flag);if (R > mid) update(rc[u], mid + 1, r ,L, R, flag);push_up(u);}public boolean query(int u, int l, int r, int L, int R) {if (u == 0) return false;           // 没被访问过,说明没被监听if (L <= l && r <= R) {return c[u];}push_down(u);int mid = (l + r) >> 1;boolean ans = true;if (L <= mid) ans &= query(lc[u], l, mid, L, R);if (R > mid) ans &= query(rc[u], mid + 1, r ,L, R);return ans;}public RangeModule() {c = new boolean[maxn];add = new boolean[maxn];lc = new int[maxn];rc = new int[maxn];}public void addRange(int left, int right) {update(root, 1, MAXN, left, right - 1, true);}public boolean queryRange(int left, int right) {return query(root, 1, MAXN, left, right - 1);}public void removeRange(int left, int right) {update(root, 1, MAXN, left, right - 1, false);}
}/*** Your RangeModule object will be instantiated and called as such:* RangeModule obj = new RangeModule();* obj.addRange(left,right);* boolean param_2 = obj.queryRange(left,right);* obj.removeRange(left,right);*/

3. 矩形面积

package dataStructure;import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Array;
import java.util.*;/*** @author: Zekun Fu* @date: 2023/2/25 10:01* @Description: 扫描线* 1. 扫描线很难扩展* 2.* 扫描线:左边加上,右边减去。* x 排序** 区间修改,区间查询* 性质:* 1. 永远只考虑根节点的信息 -> 查询不用push_down了。* 2. 操作都是成对出现, 且先加后减-> 修改的时候不用push_down了*              减法的时候*                  1. 恰好成对出现***              加法操作:*                  1. cnt > 0: 不push_down也不用影响结果*                  2. cnt == 0: 懒标记不用下推** 由于有double,所以离散化,用unique来做**  线段树*  1. 存储线段, yl-1 表示 yl-1到y, 所以需要把yl到yr-1进行 + diff就行了*  1.1 修改区间,就是修改纵坐标的区间。*  1.2 求和,只求头的和*  2. cover和*  3. segment:离散化 + 扫描线的产物*  4. ys, y坐标离散化的产物,保留了。*  5. push_up操作*      1. 如果cover等于1,说明完全被覆盖,直接就是当前区间的代表现段总长。*      2. 如果**/
public class Acwing_247 {private static double[] ys, len;private static int[] cover;private static Segment[] seg;private static int idx;private static void init(int n) {int m = n * 2;ys = new double[m];len = new double[m * 4];cover = new int[m * 4];seg = new Segment[m];idx = 0;}// 使用double进行离散化,很困难啊。// 如果单单使用mp进行离散化,那么离散化之后的高度,就不知道等于多少了// 这里使用双向映射,就可以知道idx对应的高度是多少了。private static void push_up (int u, int l, int r) {if (cover[u] == 1) {len[u] = ys[r + 1] - ys[l];} else if (l == r) len[u] = 0;      // 因为没有push_down,所以碰见叶子结点,从push_up中修改成0else len[u] = len[u << 1] + len[u << 1 | 1];}private static void update(int u, int l, int r, int L, int R, int d) {if (L > R) throw new NoSuchElementException(String.format("输入应该保证L <= R但是给定[L, R] = [%d, %d]", L, R));if (L > r || R < l) throw new NoSuchElementException(String.format("区间[L, R]应该在区间[l, r]之内但是给定[L, R] = [%d %d], [l, r] = [%d, %d]", L, R, l, r));if (L <= l && r <= R) {cover[u] += d;push_up(u, l, r);           // push_up代替了push_downreturn ;}int mid = l + r >> 1;if (L <= mid) update(u << 1, l, mid, L, R, d);if (R > mid) update(u << 1 | 1, mid + 1, r, L, R, d);push_up(u, l, r);}private static class Segment implements Comparable<Segment>{public double x, y1, y2;public int diff;@Overridepublic int compareTo(Segment o) {return Double.compare(x, o.x);}public Segment(double x, double y1, double y2, int diff) {this.x = x;this.y1 = y1;this.y2 = y2;this.diff = diff;}}private static double[] unique(double []sortNums) {double eps = 1e-9;int n = sortNums.length;int j = 0;for(int i = 0;i < n;i++) {if (i == 0 || Math.abs(sortNums[i] - sortNums[i - 1]) > eps) {sortNums[j] = sortNums[i];j++;}}double[] ans = new double[j];for (int i = 0; i < j; i++) {ans[i] = sortNums[i];}return ans;}public static int find(double aIndex){double eps = 1e-9;int l = 0;int r = ys.length;while(l < r){int mid = l + r >>1;if(ys[mid] - aIndex >= eps) r = mid;else l = mid + 1;}return l - 1;}public static void main(String[] args) throws Exception{int T = 1;BufferedReader in = new BufferedReader(new InputStreamReader(System.in));BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));int n = Integer.parseInt(in.readLine().split(" ")[0]);while (n != 0) {init(n);String[] input;for (int i = 0, j = 0, k = 0; i < n; i++) {input = in.readLine().split(" ");double x1 = Double.parseDouble(input[0]);double y1 = Double.parseDouble(input[1]);double x2 = Double.parseDouble(input[2]);double y2 = Double.parseDouble(input[3]);seg[j ++] = new Segment(x1, y1, y2, 1);seg[j ++] = new Segment(x2, y1, y2, -1);ys[k++] = y1;ys[k++] = y2;}// 离散化, 为了获取idx对应的高度,双向映射Arrays.sort(ys);Arrays.sort(seg);ys = unique(ys);idx = ys.length;int m = 2 * n;double res = 0 ;for (int i = 0; i < m; i++) {if (i != 0) res += len[1] * (seg[i].x - seg[i - 1].x);int l = find(seg[i].y1), r = find(seg[i].y2);update(1,0, idx - 1, l, r - 1, seg[i].diff);}out.write(String.format("Test case #%d\n", T++));out.write(String.format("Total explored area: %.2f\n\n", res));n = Integer.parseInt(in.readLine().split(" ")[0]);}out.flush();out.close();in.close();}
}

使用Map进行离散化


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;/*** @author: Zekun Fu* @date: 2023/2/25 10:01* @Description: 扫描线* 1. 扫描线很难扩展* 2.* 扫描线:左边加上,右边减去。* x 排序** 区间修改,区间查询* 性质:* 1. 永远只考虑根节点的信息 -> 查询不用push_down了。* 2. 操作都是成对出现, 且先加后减-> 修改的时候不用push_down了*              减法的时候*                  1. 恰好成对出现***              加法操作:*                  1. cnt > 0: 不push_down也不用影响结果*                  2. cnt == 0: 懒标记不用下推**  线段树*  1. 存储线段, yl-1 表示 yl-1到y, 所以需要把yl到yr-1进行 + diff就行了*  1.1 修改区间,就是修改纵坐标的区间。*  1.2 求和,只求头的和*  2. cover和*  3. segment:离散化 + 扫描线的产物*  4. ys, y坐标离散化的产物,保留了。*  5. push_up操作*      1. 如果cover等于1,说明完全被覆盖,直接就是当前区间的代表现段总长。*      2. 如果**/
public class Main {private static double[] ys, len;private static int[] cover;private static Segment[] seg;private static HashMap<Double, Integer>mp;private static HashMap<Integer, Double>height;private static int idx;private static void init(int n) {int m = n * 2;ys = new double[m];len = new double[m * 4];cover = new int[m * 4];seg = new Segment[m];mp = new HashMap<>();height = new HashMap<>();idx = 0;}// 如果单单使用mp进行离散化,那么离散化之后的高度,就不知道等于多少了// 这里使用双向映射,就可以知道idx对应的高度是多少了。private static void push_up (int u, int l, int r) {if (cover[u] >= 1) {if (!height.containsKey(r + 1))throw new NoSuchElementException(String.format("没有r = %d这个key。r应该是现段左端点,%d是否越界了!", r + 1, r + 1));if (!height.containsKey(l))throw new NoSuchElementException(String.format("L = %d太小了", l));len[u] = height.get(r + 1) - height.get(l);} else if (l == r) len[u] = 0;      // 因为没有push_down,所以碰见叶子结点,从push_up中修改成0else len[u] = len[u << 1] + len[u << 1 | 1];}private static void update(int u, int l, int r, int L, int R, int d) {if (L > R) throw new NoSuchElementException(String.format("输入应该保证L <= R但是给定[L, R] = [%d, %d]", L, R));if (L > r || R < l) throw new NoSuchElementException(String.format("区间[L, R]应该在区间[l, r]之内但是给定[L, R] = [%d %d], [l, r] = [%d, %d]", L, R, l, r));if (L <= l && r <= R) {cover[u] += d;push_up(u, l, r);           // push_up代替了push_downreturn ;}int mid = l + r >> 1;if (L <= mid) update(u << 1, l, mid, L, R, d);if (R > mid) update(u << 1 | 1, mid + 1, r, L, R, d);push_up(u, l, r);}private static class Segment implements Comparable<Segment>{public double x, y1, y2;public int diff;@Overridepublic int compareTo(Segment o) {return Double.compare(x, o.x);}public Segment(double x, double y1, double y2, int diff) {this.x = x;this.y1 = y1;this.y2 = y2;this.diff = diff;}}public static void main(String[] args) throws Exception{int T = 1;BufferedReader in = new BufferedReader(new InputStreamReader(System.in));BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));int n = Integer.parseInt(in.readLine().split(" ")[0]);while (n != 0) {init(n);String[] input;for (int i = 0, j = 0, k = 0; i < n; i++) {input = in.readLine().split(" ");double x1 = Double.parseDouble(input[0]);double y1 = Double.parseDouble(input[1]);double x2 = Double.parseDouble(input[2]);double y2 = Double.parseDouble(input[3]);seg[j ++] = new Segment(x1, y1, y2, 1);seg[j ++] = new Segment(x2, y1, y2, -1);ys[k++] = y1;ys[k++] = y2;}// 离散化, 为了获取idx对应的高度,双向映射Arrays.sort(ys);Arrays.sort(seg);int m = 2 * n;for (int i = 0; i < m; i++) {if (mp.containsKey(ys[i])) continue;mp.put(ys[i], ++idx);height.put(idx, ys[i]);}double res = 0 ;for (int i = 0; i < m; i++) {if (i != 0) res += len[1] * (seg[i].x - seg[i - 1].x);int l = mp.get(seg[i].y1), r = mp.get(seg[i].y2);update(1,1, idx, l, r - 1, seg[i].diff);}out.write(String.format("Test case #%d\n", T++));out.write(String.format("Total explored area: %.2f\n\n", res));n = Integer.parseInt(in.readLine().split(" ")[0]);}out.flush();out.close();in.close();}
}

4. 天际线

  • 左加右减
  • 同一批次处理
  • 优先队列 + map找最大值
package leetcode.categories.dataStructure.xianduanshu;import leetcode.utils.ChangeToArrayOrList;
import leetcode.utils.PrintArrays;import java.lang.reflect.Array;
import java.util.*;/*** @author: Zekun Fu* @date: 2023/2/27 15:20* @Description: 天际线** 1. 扫描线* 2. 线段树*/
public class Leet218 {private class Segment implements Comparable<Segment>{public int x, y;public int diff;public Segment(int x, int y, int diff) {this.x = x;this.y = y;this.diff = diff;}@Overridepublic int compareTo(Segment o) {return Integer.compare(this.x, o.x);}}public List<List<Integer>> getSkyline(int[][] buildings) {int n = buildings.length;int m = n * 2;Segment[] seg = new Segment[m];for (int i = 0, j = 0; i < n; i++) {int[] build =  buildings[i];int x1 = build[0], x2 = build[1], h = build[2];seg[j++] = new Segment(x1, h, 1);seg[j++] = new Segment(x2, h, -1);}Arrays.sort(seg);List<List<Integer>> ans = new ArrayList<>();HashMap<Integer, Integer>mp = new HashMap<>();PriorityQueue<Integer>que = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {if (o1 > o2) return -1;                                     // 自动拆包else if (o1.equals(o2)) return 0;else return 1;}});que.add(0);mp.put(0, 1);int pre = 0;for (int i = 0; i < m; ) {int j = i;// 处理相同的边界while (j < m && seg[i].x == seg[j].x) {int h = seg[j].y, diff = seg[j].diff;if (mp.containsKey(h)) mp.put(h, mp.get(h) + diff);else mp.put(h, diff);// 入队if (mp.get(h) > 0) que.add(h);// 出队while (!que.isEmpty() && mp.getOrDefault(que.peek(), 0) == 0) que.poll();j++;}if (pre != que.peek()) {pre = que.peek();Integer[] tmp = new Integer[]{seg[i].x, pre};ans.add(Arrays.asList(tmp));}i = j;}return ans;}public static void main(String[] args) {Leet218 leet218 = new Leet218();String arr = "[[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]";int[][]tmp = ChangeToArrayOrList.changeTo2DIntArray(arr);List<List<Integer>>list = leet218.getSkyline(tmp);for (List<Integer>t : list) {PrintArrays.print1DObjArray(t.toArray(new Integer[t.size()]));}}
}
package leetcode.categories.dataStructure.xianduanshu;import leetcode.utils.ChangeToArrayOrList;
import leetcode.utils.PrintArrays;import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** @author: Zekun Fu* @date: 2023/2/27 16:13* @Description:* 天际线线段树解法*/
public class leet218_2 {private final int maxn = (int)4e6 + 5;private int[] lc, rc, maxv, flag;private int idx = 1, root = 1;          // 从push_down中开点,所以从1开始private void push_up(int u) {maxv[u] = Math.max(maxv[lc[u]], maxv[rc[u]]);}// 只有在完全包含这个区间的时候,才会进行push_down(u, x)操作。private void push_down(int u, int x) {maxv[u] = Math.max(maxv[u], x);flag[u] = Math.max(flag[u], x);}private void push_down(int u) {// 动态开点if (lc[u] == 0) lc[u] = ++idx;if (rc[u] == 0) rc[u] = ++idx;if (flag[u] == 0) return ;push_down(lc[u], flag[u]);push_down(rc[u], flag[u]);flag[u] = 0;}public void update(int u, int l, int r, int L, int R, int x) {if (L > R) throw new IllegalArgumentException(String.format("L = %d > R = %d!", L, R));if (L > r || R < l) throw new IllegalArgumentException(String.format("[L, R] = [%d %d] & [l, r] = [%d %d] == null", L, R, l, r));if (L <= l && r <= R) {push_down(u, x);return ;}push_down(u);int mid = (int)(((long)l + r) >> 1);if (L <= mid) update(lc[u], l, mid, L, R, x);if (R > mid) update(rc[u], mid + 1, r, L, R, x);push_up(u);}public int query(int u, int l, int r, int L, int R) {if (L > R) throw new IllegalArgumentException("L > R!");if (L > r || R < l) throw new IllegalArgumentException(String.format("[L, R] = [%d %d] & [l, r] = [%d %d] == null", L, R, l, r));if (u == 0) return 0;                                       // 没被访问,就一定没有值if (L <= l && r <= R) {return maxv[u];}push_down(u);int mid = (int)(((long)l + r) >> 1);int ans = 0;                                            // 注意是否long, 是否从0开始if (L <= mid) ans = query(lc[u], l, mid, L, R);if (R > mid) ans = Math.max(ans, query(rc[u], mid + 1, r, L, R));return ans;a}private void init() {this.flag = new int[maxn];this.maxv = new int[maxn];this.lc = new int[maxn];this.rc = new int[maxn];}public List<List<Integer>> getSkyline(int[][] buildings) {// 1. 线段树动态开点// 2. 对于每一个building,让区间[l, r - 1]整体修改成h// 3. 遍历所有的x// 4. 碰见最大值和前面不相同,就记录[x, cur]// 当前的最大高度取决于左边界的高度init();int maxv = Integer.MAX_VALUE;                   //[0, maxv]一定包含全部[0, maxv - 1]List<Integer>xs = new ArrayList<>();for (int[] build : buildings) {update(root, 0, maxv, build[0], build[1] - 1, build[2]);xs.add(build[0]);xs.add(build[1]);}int pre = 0;List<List<Integer>> ans = new ArrayList<>();Collections.sort(xs);for (int x : xs) {int h = query(root, 0, maxv, x, x);Integer[] tmp = new Integer[]{x, h};if (h != pre) ans.add(Arrays.asList(tmp));pre = h;}return ans;}public static void main(String[] args) {leet218_2 leet218_2 = new leet218_2();String arr = "[[0,2,3],[2,5,3]]";int[][]tmp = ChangeToArrayOrList.changeTo2DIntArray(arr);List<List<Integer>>list = leet218_2.getSkyline(tmp);for (List<Integer>t : list) {PrintArrays.print1DObjArray(t.toArray(new Integer[t.size()]));}}
}

调试技巧

  1. query和add分别加上两句话防止使用出错!
query() {if (L > R) throw new NoSuchElementException("区间L 应该 小于R");if (L > r || R < l) throw new NoSuchElementException("区间[L, R]和区间[l, r]应该有交集![可能是l,r不能包含所有区间!]");
}
update() {if (p < l || p > r) throw new NoSuchElementException("p越界了,p应该在区间中");
}
  1. 最容易错的还是l和R,可以直接复制粘贴。只修改Push_down和push_up

问题以及注意事项

  1. 天际线和方块的问题中,为什么需要右端点-1,而不是左端点 + 1呢?
  2. 具体的调试技巧,看上面的维护序列。
  3. 注意使用的时候,区间从1开始,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75341.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【unity】开发rts 3 出生点,创建建筑物

一 出生点、阵营类型、阵营 实例栏-GameManage&#xff0c;默认有一个插槽 size 插槽数量 role 权限&#xff0c;host是主人&#xff0c;权限高 type 阵营类型&#xff0c;不选不限制&#xff0c;选的效果没看懂&#xff0c;文档原文&#xff1a; The Type field in Data al…

Cookie、Session、JWT 那些事

文章目录前言一、概念1、Cookie&#xff1a;2、Session&#xff1a;3、JWT二、应用1. 基本使用2. 实现 “退出” 功能总结前言 目前 C/S 模式盛行&#xff0c;HTTP 是其中最常见的通信协议&#xff0c;我们知道 HTTP 协议是无状态的&#xff0c;但是这场景完全不够用。 比如&…

Python|每日一练|算法初阶|字符串|树|深度优先搜索|单选记录:循环随机取数组直到得出指定数字|有效数字|平衡二叉树

1、循环随机取数组直到得出指定数字&#xff1f;&#xff08;算法初阶&#xff09; 贡献者&#xff1a;weixin_30937093 举个例子&#xff1a; 随机数字范围&#xff1a;0~100 每组数字量&#xff1a;6&#xff08;s1,s2,s3,s4,s5,s6&#xff09; 第二轮开始随机数字范围&…

Linux 基础介绍-基础命令

文章目录01 学习目标02 Linux/Unix 操作系统简介2.1 Linux 操作系统的目标2.2 Linux 操作系统的作用2.3 Unix 家族历史2.4 Linux 家族历史2.5 Linux 和Unix 的联系2.6 Linux 内核介绍2.7 Linux 发行版本2.8 Unix/Linux 开发应用领域介绍03 Linux 目录结构3.1 Win 和Linux 文件系…

Mac iTerm2 rz sz

1、安装brew&#xff08;找了很多&#x1f517;&#xff0c;就这个博主的好用&#xff09; Mac如何安装brew&#xff1f;_行走的码农00的博客-CSDN博客_mac brew 2、安装lrzsz brew install lrzsz 检查是否安装成功 brew list 定位lrzsz的安装目录 brew list lrzsz 执…

git学习记录/菜鸟教程(基于Gitcode)

首先说明下为何使用Gitcode而不是hub或lab&#xff1a;只是因为国外的网站访问太慢了&#xff0c;而且还要翻译从初次使用开始说&#xff1a;首先安装Git&#xff0c;一路next就可以&#xff0c;安装好后打开&#xff0c;输入git version如果有显示版本号&#xff0c;说明安装成…

2020蓝桥杯真题跑步锻炼(填空题) C语言/C++

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小蓝每天都锻炼身体。 正常情况下&#xff0c;小蓝每天跑 1 千米。如果某天是周一或者月初&#xff08;1 日&#xff09;&#xff0c;为了激励自己&#xff0c;小蓝…

Docker在Windows环境的搭建和使用

文章目录安装WSL安装Docker安装Docker镜像下载Docker镜像启动gpu启动传送文件训练yolov5安装WSL Windows10和11支持Docker的安装&#xff0c;安装需要用到WSL。所以&#xff0c;我们先安装WSL。 参考文章&#xff1a;旧版 WSL 的手动安装步骤 以管理员身份打开powershell, 执行…

软考信息系统监理师备考建议

用好备考方法&#xff0c;两三个月就可以过的。信息系统监理师备考最好以教材和历年真题为主&#xff0c;教学视频模拟题为辅。考试介绍与复习建议&#xff1a;考试设置的科目包括&#xff1a;&#xff08;1&#xff09;信息系统工程监理基础知识&#xff0c;考试时间150分钟&a…

Three.js初试——基础概念

一、Three.js 是什么 先附上文档&#xff1a; 官网&#xff1a;JavaScript 3D Library 中文文档&#xff1a;中文文档 Three.js 是一个让用户通过 javascript 入手进入搭建 WebGL 项目的类库。众所周知学习 WebGL 需要图形学知识&#xff0c;而 webgl 需要通过 js 和 glsl …

第八届蓝桥杯省赛——4承压计算(二维数组,嵌套循环)

题目&#xff1a;X星球的高科技实验室中整齐地堆放着某批珍贵金属原料。每块金属原料的外形、尺寸完全一致&#xff0c;但重量不同。金属材料被严格地堆放成金字塔形。7 5 8 7 8 8 9 2 7 2 8 1 4 9 1 8 1 8 8 4 1 7 9 6 1 4 5 4 5 6 5 5 6 9 5 6 5 5 4 7 9 3 5 5 1 7 5 7 9 7 4…

车辆热管理测试方案

车辆热管理是在能源危机出现、汽车排放法规日益严格以及人们对汽车舒适性要求更高的背景下应运而生的。将各个系统或部件如冷却系统、润滑系统和空调系统等集成一个有效的热管理系统&#xff1b;控制和优化车辆的热量传递过程&#xff0c;保证各关键部件和系统安全高效运行&…

社交媒体营销的5个好处

有些人认为&#xff0c;社交媒体营销不能直接与销售挂钩。这就是为什么在制定营销策略时&#xff0c;社交媒体营销会被部分人忽视的原因。然而&#xff0c;与其他广告渠道不同&#xff0c;社交媒体是双向渠道。忽视社交媒体营销将影响与客户的关系。最重要的是&#xff0c;它将…

回顾1-idea创建Java项目

创建Java项目 创建项目和模块的区别 环境前置 IDEA开发工具JDK及配置环境变量 创建项目/工程 新建项目 选择Java模块 > SDK( 已配置的JDK ) > 下一步 直接下一步 填写项目信息 QQ游戏工程 里的 叫项目 所以 QQgame目录下 可以放 > 斗地主项目 / 美女来找茬等… …

C while 循环for循环

C 循环 只要给定的条件为真&#xff0c;C 语言中的 while 循环语句会重复执行一个目标语句。 语法 C 语言中 while 循环的语法&#xff1a; while(condition) {statement(s); }在这里&#xff0c;statement(s) 可以是一个单独的语句&#xff0c;也可以是几个语句组成的代码块…

深度学习基础实例与总结

一、神经网络 1 深度学习 1 什么是深度学习&#xff1f; 简单来说&#xff0c;深度学习就是一种包括多个隐含层 (越多即为越深)的多层感知机。它通过组合低层特征&#xff0c;形成更为抽象的高层表示&#xff0c;用以描述被识别对象的高级属性类别或特征。 能自生成数据的中…

DNS服务器部署的详细操作(图文版)

DNS服务器的部署 打开虚拟机后查看已经开放的端口&#xff0c;可以看到没有TCP53、UDP53&#xff0c;说明DNS服务端口没有打开 打开我的电脑—双击CD驱动器— 选择安装可选的Windows组件 选择网络服务—域名系统&#xff08;DNS&#xff09;— 点击下一步后会弹出如下弹…

线程安全实例分析

一、变量的线程安全分析 成员变量和静态变量是否线程安全&#xff1f; ● 如果它们没有共享&#xff0c;则线程安全 ● 如果它们被共享了&#xff0c;根据它们的状态是否能够改变&#xff0c;又分两种情况 —— 如果只有读操作&#xff0c;则线程安全 —— 如果有读写操作&am…

实时手势识别(C++与python都可实现)

一、前提配置&#xff1a; Windows&#xff0c;visual studio 2019&#xff0c;opencv&#xff0c;python10&#xff0c;opencv-python&#xff0c;numpy&#xff0c;tensorflow&#xff0c;mediapipe&#xff0c;math 1.安装python环境 这里我个人使用的安装python10&#…

ABB机器人基础编程_常见数据类型及使用方法介绍

ABB机器人基础编程_常见数据类型及使用方法介绍 1. bool-逻辑值 描述:bool型数据可以为TRUE或FALSE 使用方法举例: 2. 字节-整数值 描述:byte用于符合字节范围的整数值0-255,该数据类型连同处理操作并转换特征的指令和函数一同使用。 使用方法举例: 3. dnum-双数值 描…