扫描线

在扫描线中,对于区间的表示有些不同,因此 \(build\) 的过程以及表示都会有些不同,这里【link】 有两种扫描线的线段树的构建方式,可以作为参考


扫描线是对线段树的一种应用,做法是逐个扫描每个矩形的上下边,并用线段树维护图形的宽,以此得到整个图形覆盖的面积

1. 需要存储的东西

每个矩形的上下边,去重过的横坐标,带标记(区间覆盖次数)的线段树

const int N = 2E5 + 10;
struct L {
  int x1, x2, y, k;
  bool operator<(const L &t) const{return y < t.y; }
}line[N];

struct Node {
  int l, r, cnt, len;
}tr[N << 3];
int n, X[N];

2. 预处理

\(x\) 坐标离散化,记录覆盖与取消覆盖的边

cin >> n;

for (int i = 1; i <= n; i ++) {
  int a, b, c, d;
  cin >> a >> b >> c >> d;
  X[i] = a, X[i + n] = c;
  line[i] = {a, c, b, 1};
  line[i + n] = {a, c, d, -1};
}

n <<= 1;
sort(X + 1, X + n + 1);
sort(line + 1, line + n + 1);
int s = unique(X + 1, X + n + 1) - X - 1;

3. 建立连续的线段树

文首有两种建树方式,第二种比较形象,但不如第一种方便,我们用第一种,即“右端点偏移”的方式建树

如果用传统的线段树建立方法,\(Node.l\)\(Node.r\) 表示线段的左右端点

build(ls, l, mid);
build(rs, mid + 1, r);

中间会有一个空隙,\(mid\)\(mid + 1\) 这两个点中间有一个线段是无法被包含的,并且\(tr[p].l == tr[p].r\) 时也无实际意义

那么,我们可以将右端点偏移,\(tr[p].r\) 来表示右端点为 \(X[tr[p].r + 1]\) 这个点,我们发现,这样可以表示每个线段了

这种偏移对于之后的计算有什么影响?

当修改 \(l\)\(r\) 的线段时,对应到线段树中是 \(r - 1\)\(update(1, l, r - 1, k)\);

建立线段树时,\(tr[p].r\) 最大为 \(s - 1\), 所以 \(build(1, 1, s - 1);\)

4. \(pushup\) 的写法

在扫描线中,只要线段被覆盖,即 \(tr[p].cnt != 0\),此时覆盖的长度就是这个线段的总长

否则,线段被覆盖的长度是两个儿子节点被覆盖长度的和

void upshup(int p) {
  if (tr[p].cnt) tr[p].len = X[tr[p].r + 1] - X[tr[p].l];
  else tr[p].len = tr[ls].len + tr[rs].len;
}

5. 区间修改

对于区间进行修改时,由于有可能改变区间覆盖的次数,会在更新线段后 \(pushup\) 一下,更新当前线段覆盖长度

这里也是寻常线段树开 \(4\) 倍空间,而扫描线需要开 \(8\) 倍的原因,可能会在叶子节点\((tr[p].l == tr[p].r)\) 再往后考虑它的儿子节点,可以发现,这是超出线段树 \(4\) 倍空间的操作

void update(int p, int l, int r, int k) {
  if (l <= tr[p].l && tr[p].r <= r) {
    tr[p].cnt += k;
    pushup(p);
    return;
  }
  int mid = tr[p].l + tr[p].r >> 1;
  if (l <= mid) update(ls, l, r, k);
  if (r > mid) update(rs, l, r, k);
  pushup(p);
}

6. 计算面积

接下来就要计算面积了,面积的计算方法是由下至上遍历扫描线,利用二分找到离散化后的线段,更新后用这个长度乘高(与下一条扫描线的高度差)

直接上代码吧

// 1 or 2 都可以
long long ans = 0;
// 1
for (int i = 1; i <= n; i ++) {
  ans += 1LL * tr[1].len * (line[i].y - line[i - 1].y);
  int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
  int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
  update(1, l, r - 1, line[i].k);
}
// 2
for (int i = 1; i < n; i ++) {
  int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
  int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
  update(1, l, r - 1, line[i].k);
  ans += 1LL * tr[1].len * (line[i].y - line[i - 1].y);
}

完整代码

#include <bits/stdc++.h>
#define ls (p << 1)
#define rs (p << 1 | 1)

using namespace std;

const int N = 2e5 + 10;

typedef long long ll;

struct L {
    int x1, x2, y, k;
    bool operator<(const L &t)const {return y < t.y; }
}line[N];

struct Node {
    int l, r, cnt, len;
}tr[N << 3];
int X[N], n;

void build(int p, int l, int r) {
    tr[p] = {l, r, 0, 0};
    if (l == r) return;
    int mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void pushup(int p) {
    if (tr[p].cnt) tr[p].len = X[tr[p].r + 1] - X[tr[p].l];
    else tr[p].len = tr[ls].len + tr[rs].len;
}

void update(int p, int l, int r, int k) {
    if (l <= tr[p].l && tr[p].r <= r) {
        tr[p].cnt += k;
        pushup(p);
        return;
    }
    int mid = tr[p].l + tr[p].r >> 1;
    if (l <= mid) update(ls, l, r, k);
    if (r > mid) update(rs, l, r, k);
    pushup(p);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;

    for (int i = 1; i <= n; i ++) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        line[i] = {a, c, b, 1};
        line[i + n] = {a, c, d, -1};
        X[i] = a, X[i + n] = c;
    }

    n <<= 1;
    sort(X + 1, X + n + 1);
    sort(line + 1, line + n + 1);
    int s = unique(X + 1, X + n + 1) - X - 1;
    build(1, 1, s - 1);
    
    ll ans = 0;
    for (int i = 1; i <= n; i ++) {
        ans += 1LL * (line[i].y - line[i - 1].y) * tr[1].len;
        int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
        int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
        update(1, l, r - 1, line[i].k); 
    }

    cout << ans << endl;

    return 0;
}
posted @ 2025-03-05 13:13  he_jie  阅读(28)  评论(0)    收藏  举报