扫描线

简介

扫描线,顾名思义,就是一根线扫过去。

矩形面积并

给定平面直角坐标系上 \(N\) 个矩形,每个矩形的边均平行于 \(x\) 轴或 \(y\) 轴。求这些矩形的面积并。(被多个矩形覆盖的区域只算一遍)

首先考虑一维上的问题:有 \(N\) 条线段,求这些线段的长度并。比如下图:

image

令线段左端点为 \(1\),右端点为 \(-1\)

现在我们再画一条垂直于 \(x\) 轴的扫描线,并让它扫过所有线段的端点:

  • 当扫到 \(A\) 时,之前和都为 \(0\),所以对答案没有贡献。
  • 当扫到 \(C\) 时,之前和都为 \(1\),所以对答案造成 \(3-1=2\) 的贡献。
  • 当扫到 \(B\) 时,之前和都为 \(2\),所以对答案造成 \(4-3=1\) 的贡献。
  • 当扫到 \(D\) 时,之前和都为 \(1\),所以对答案造成 \(5-4=1\) 的贡献。
  • 当扫到 \(E\) 时,之前和都为 \(0\),所以对答案没有贡献。
  • 当扫到 \(G\) 时,之前和都为 \(1\),所以对答案造成 \(8-7=1\) 的贡献。
  • 当扫到 \(I\) 时,之前和都为 \(2\),所以对答案造成 \(9-8=1\) 的贡献。
  • 当扫到 \(H\) 时,之前和都为 \(3\),所以对答案造成 \(10-9=1\) 的贡献。
  • 当扫到 \(F\) 时,之前和都为 \(2\),所以对答案造成 \(11-10=1\) 的贡献。
  • 当扫到 \(J\) 时,之前和都为 \(2\),所以对答案造成 \(12-11=1\) 的贡献。

这就是用扫描线解决该问题的思路。

我们可以将这些矩形看成很多条线段,就像这样:

image

这时,我们就把二维问题转化成了许多一维问题。

而我们可以用一个线段树维护所有的和的最小值和数量,而查询非 \(0\) 值的数量可以转化为:

  • 若最小值为 \(0\),则非零元素数量为所有元素数量 \(-\) 最小值数量。
  • 否则,非零元素数量为所有元素数量。

如果 \(y\) 很大,需要离散化。

时间复杂度 \(O(N\log N)\),空间复杂度 \(O(N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXN = 200001;

struct Node {
  int x, l, r, op;
}s[MAXN];

struct INFO {
  int Min, cnt;
  INFO operator+(const INFO &a) {
    return INFO{min(Min, a.Min), (Min <= a.Min ? cnt : 0) + (a.Min <= Min ? a.cnt : 0)};
  }
  INFO operator+(const int &a) {
    return INFO{Min + a, cnt};
  }
  INFO operator+=(const INFO &a) {
    return *this = *this + a;
  }
  INFO operator+=(const int &a) {
    return *this = *this + a;
  }
};

struct Segment_Tree {
  int l[4 * MAXN], r[4 * MAXN], lazy[4 * MAXN], y[2 * MAXN];
  INFO v[4 * MAXN];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t, lazy[u] = 0;
    if(s == t) {
      v[u] = INFO{0, y[s] - y[s - 1]};
      return;
    }
    int mid = (s + t) >> 1;
    build(2 * u, s, mid), build(2 * u + 1, mid + 1, t);
    v[u] = v[2 * u] + v[2 * u + 1];
  }
  void tag(int u, int x) {
    v[u] += x, lazy[u] += x;
  }
  void pushdown(int u) {
    tag(2 * u, lazy[u]), tag(2 * u + 1, lazy[u]), lazy[u] = 0;
  }
  void update(int u, int s, int t, int x) {
    if(l[u] >= s && r[u] <= t) {
      tag(u, x);
      return;
    }
    pushdown(u);
    if(s <= r[2 * u]) {
      update(2 * u, s, t, x);
    }
    if(t >= l[2 * u + 1]) {
      update(2 * u + 1, s, t, x);
    }
    v[u] = v[2 * u] + v[2 * u + 1];
  }
  INFO GetInfo(int u, int s, int t) {
    if(l[u] >= s && r[u] <= t) {
      return v[u];
    }
    pushdown(u);
    INFO x = INFO{INT_MAX, 0};
    if(s <= r[2 * u]) {
      x += GetInfo(2 * u, s, t);
    }
    if(t >= l[2 * u + 1]) {
      x += GetInfo(2 * u + 1, s, t);
    }
    return x;
  }
}tr;

int n, tot;
ll ans;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1, x, y, x2, y2; i <= n; ++i) {
    cin >> x >> y >> x2 >> y2;
    s[i] = {x, y, y2, 1};
    s[n + i] = {x2, y, y2, -1};
    tr.y[i] = y;
    tr.y[n + i] = y2;
  }
  sort(tr.y + 1, tr.y + 2 * n + 1);
  for(int i = 1; i <= 2 * n; ++i) {
    if(!tot || tr.y[i] > tr.y[tot]) {
      tr.y[++tot] = tr.y[i];
    }
  }
  for(int i = 1; i <= 2 * n; ++i) {
    s[i].l = lower_bound(tr.y + 1, tr.y + tot + 1, s[i].l) - tr.y;
    s[i].r = lower_bound(tr.y + 1, tr.y + tot + 1, s[i].r) - tr.y;
  }
  sort(s + 1, s + 2 * n + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
  tr.build(1, 1, tot);
  for(int i = 1; i <= 2 * n; ++i) {
    INFO x = tr.GetInfo(1, 1, tot);
    ans += 1ll * (s[i].x - s[i - 1].x) * (x.Min ? tr.y[tot] : tr.y[tot] - x.cnt);
    tr.update(1, s[i].l + 1, s[i].r, s[i].op);
  }
  cout << ans;
  return 0;
}

posted @ 2024-07-20 16:35  Yaosicheng124  阅读(2)  评论(0编辑  收藏  举报