Scanline

Introduction

A scanline is commonly used for geometric figures. As the name suggests, a line sweeps across the figure, and the technique is used for areas, perimeters, and two-dimensional point counting.

Union Area of Rectangles

Given the lower-left and upper-right coordinates of several rectangles in the plane, compute the area of their union.

Procedure

The area can be found by brute force for a small instance. What if the input is large? This is where the scanline algorithm is useful.

Imagine a line sweeping upward from below:

Figure: scanning

As shown in the figure, divide the rectangle into small rectangles of different colors. Their heights are the distances swept by the line, while their horizontal widths change.

Mark the lower and upper edge of each rectangle with 1 and -1, respectively. Whenever the sweep meets a horizontal edge, add its mark to the weight of the interval covered by that edge on the x-axis.

Note

This resembles traversing a parenthesis sequence: an opening parenthesis adds 1, a closing parenthesis subtracts 1, the “weight” is the current depth, and a positive weight means that the interval is inside parentheses and contributes to the small-rectangle width.

The total width of the small rectangles is the total length of the intervals on the number line whose weight is positive.

Implementation

Use a segment tree to maintain the length covered by a positive number of rectangles. The requirements are:

  • Add 1 or subtract 1 from every weight in an interval.
  • Compute the total length of the number line whose weight is positive.

A direct segment-tree template can be troublesome. When adding to an interval, even if the modified interval matches a segment-tree node exactly, we cannot determine in constant time how the covered length changes: we do not know how much of the managed interval changes from 1 to 0 or from 0 to 1.

A simple divide-and-conquer solution suffices: maintain v[], the number of fully covering intervals for each node (like a lazy tag that is not pushed down), and w[], the covered length.

Coordinate compression is required.

Luogu P5490 [Template] Scanline & Union Area of Rectangles — reference implementation
#include <algorithm>
#include <iostream>

using ll = long long;
constexpr int N = 1e5 + 1;

int n, a[N * 2], tot;   // a[] and tot are used for coordinate compression
ll v[N * 8], w[N * 8];  // full-cover counts and covered lengths

struct St {
  ll x1, x2, y, o;
} b[N * 2];  // upper and lower rectangle edges

int f(int y) {  // map a coordinate to its index in a
  return std::lower_bound(a, a + tot, y) - a;
}

void up(int u, int ul, int ur) {  // pushup
  if (v[u]) w[u] = a[ur] - a[ul];
  // Calling w[u*2+1] on a leaf requires eight times the segment-tree space
  // the two horizontal edges of each rectangle make the factor sixteen
  else if (ul + 1 == ur)
    w[u] = 0;
  else
    w[u] = w[u * 2 + 1] + w[u * 2 + 2];
}

void add(int lf, int rg, ll o, int u = 0, int ul = 0, int ur = tot - 1) {
  // add to an interval
  if (lf == ul && rg == ur) return v[u] += o, up(u, ul, ur), void();
  int um = (ul + ur) / 2;
  if (lf < um) add(lf, std::min(rg, um), o, u * 2 + 1, ul, um);
  if (um < rg) add(std::max(lf, um), rg, o, u * 2 + 2, um, ur);
  up(u, ul, ur);
}

int main() {
  std::cin >> n;
  for (int i = 0, x1, x2, y1, y2; i < n; i++) {
    // y1 is a local variable, so it cannot conflict with another name
    std::cin >> x1 >> y1 >> x2 >> y2;
    b[i] = {x1, x2, y1, 1};
    b[i + n] = {x1, x2, y2, -1};
    a[i] = x1, a[i + n] = x2;
  }

  std::sort(a, a + n * 2), tot = 1;
  for (int i = 1; i < n * 2; i++)
    if (a[i] != a[tot - 1]) a[tot++] = a[i];  // coordinate compression

  std::sort(b, b + n * 2,
            [](St &i, St &j) -> bool { return i.y < j.y; });  // sort the events

  ll sum = 0;
  add(f(b[0].x1), f(b[0].x2), 1);
  for (int i = 1; i < n * 2; i++) {
    int x1 = f(b[i].x1), x2 = f(b[i].x2);
    sum += (b[i].y - b[i - 1].y) * w[0];  // add each horizontal strip's area
    add(x1, x2, b[i].o);
  }
  std::cout << sum << '\n';
}
[POJ 1151] Atlantis — reference implementation
#include <algorithm>
#include <cstdio>
#include <cstring>
constexpr int MAXN = 300;
using namespace std;

int lazy[MAXN << 3];  // number of times this segment is covered
double s[MAXN << 3];

struct node1 {
  double l, r;
  double sum;
} cl[MAXN << 3];  // segment tree

struct node2 {
  double x, y1, y2;
  int flag;
} p[MAXN << 3];  // coordinates

// sort comparator
bool cmp(node2 a, node2 b) { return a.x < b.x; }

// pull information upward
void pushup(int rt) {
  if (lazy[rt] > 0)
    cl[rt].sum = cl[rt].r - cl[rt].l;
  else
    cl[rt].sum = cl[rt * 2].sum + cl[rt * 2 + 1].sum;
}

// build the tree
void build(int rt, int l, int r) {
  if (r - l > 1) {
    cl[rt].l = s[l];
    cl[rt].r = s[r];
    build(rt * 2, l, (l + r) / 2);
    build(rt * 2 + 1, (l + r) / 2, r);
    pushup(rt);
  } else {
    cl[rt].l = s[l];
    cl[rt].r = s[r];
    cl[rt].sum = 0;
  }
  return;
}

// update
void update(int rt, double y1, double y2, int flag) {
  if (cl[rt].l == y1 && cl[rt].r == y2) {
    lazy[rt] += flag;
    pushup(rt);
    return;
  } else {
    if (cl[rt * 2].r > y1) update(rt * 2, y1, min(cl[rt * 2].r, y2), flag);
    if (cl[rt * 2 + 1].l < y2)
      update(rt * 2 + 1, max(cl[rt * 2 + 1].l, y1), y2, flag);
    pushup(rt);
  }
}

int main() {
  int temp = 1, n;
  double x1, y1, x2, y2, ans;
  while (scanf("%d", &n) && n) {
    ans = 0;
    for (int i = 0; i < n; i++) {
      scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
      p[i].x = x1;
      p[i].y1 = y1;
      p[i].y2 = y2;
      p[i].flag = 1;
      p[i + n].x = x2;
      p[i + n].y1 = y1;
      p[i + n].y2 = y2;
      p[i + n].flag = -1;
      s[i + 1] = y1;
      s[i + n + 1] = y2;
    }
    sort(s + 1, s + (2 * n + 1));  // coordinate compression
    sort(p, p + 2 * n, cmp);  // sort rectangle edge x-coordinates
    build(1, 1, 2 * n);       // build the tree
    memset(lazy, 0, sizeof(lazy));
    update(1, p[0].y1, p[0].y2, p[0].flag);
    for (int i = 1; i < 2 * n; i++) {
      ans += (p[i].x - p[i - 1].x) * cl[1].sum;
      update(1, p[i].y1, p[i].y2, p[i].flag);
    }
    printf("Test case #%d\nTotal explored area: %.2lf\n\n", temp++, ans);
  }
  return 0;
}

Exercises

B-Dimensional Orthogonal Ranges

A B-dimensional orthogonal range is the set of points inside a B-dimensional Cartesian coordinate system whose ii-th coordinate lies in an integer interval [li,ri][l_i,r_i].

A one-dimensional orthogonal range is usually called an interval, a two-dimensional one a rectangle, and a three-dimensional one a cuboid; two-dimensional point counting is also a two-dimensional orthogonal-range problem.

For a static two-dimensional problem, scan one dimension and use a data structure to maintain the other. As the scanline moves from left to right, updates and queries occur in the dimension maintained by the data structure. If the queried information supports subtraction, use differences directly; otherwise use divide and conquer. Differences are usually maintained with a Fenwick tree or segment tree, and Fenwick trees are common because they are simple and have small constants. Divide and conquer usually means CDQ divide and conquer, which is not discussed here.

Another useful viewpoint is to regard the data as a sequence rather than a plane. The scanline then enumerates the right endpoint r=1⋯nr=1\cdots n and maintains a data structure that, for the current rr and a given ll, answers the range from ll to rr. In other words, the scanline enumerates right endpoints while the data structure stores the answers for all left endpoints.

The complexity is generally O((n+m)log⁡n)O((n+m)\log n).

Two-Dimensional Point Counting

Given a sequence of length nn and mm queries, count for each query the elements in [l,r][l,r] whose values lie in [x,y][x,y].

This is called two-dimensional point counting. It is equivalent to finding the number of points inside a rectangle in the plane. The simplest method is a scanline together with a Fenwick tree.

This is a static two-dimensional problem, which the scanline converts into a dynamic one-dimensional problem. Maintain the sequence with a data structure; here a Fenwick tree is sufficient.

First coordinate-compress all queries and use a Fenwick tree for the weights. For a query with endpoints ll and rr, when the enumeration reaches l−1l-1, record the number [x,y][x,y] of values currently in aa. Continue the enumeration and, at rr, record the number [x,y][x,y] in bb. The answer is b−ab-a.

Example

[Luogu P2163] [SHOI2007] Gardener's Troubles

First perform coordinate compression. Let (0,0)(0, 0) be the number of points in the rectangle with lower-left corner (x,y)(x, y) and upper-right corner ansx,yans_{x, y}. The query answer is the difference ansc,d−ansa−1,d−ansc,b−1+ansa−1,b−1ans_{c, d} - ans_{a - 1, d} - ans_{c, b - 1} + ans_{a - 1, b - 1}.

Code
#include <algorithm>
#include <iostream>

int n, m;
int x[500010], y[500010], ans[500010];
int ax[1500010], ay[1500010], tx, ty;  // coordinate compression

struct query {
  int a, b, c, d;
} q[500010];  // store queries for compression

struct ope {
  int type, x, y, id;

  ope(int type = 0, int x = 0, int y = 0, int id = 0) {
    this->type = type, this->x = x, this->y = y, this->id = id;
  }

  bool operator<(const ope& rhs) const {
    if (x == rhs.x) return type < rhs.type;
    return x < rhs.x;
  }
};

ope op[2500010];
int tot;  // total number of events

int sum[1500010];  // Fenwick tree

int lowbit(int x) { return x & (-x); }

void add(int x, int k) {
  while (x <= 1500000) {
    sum[x] = sum[x] + k;
    x = x + lowbit(x);
  }
}

int getsum(int x) {
  int ret = 0;
  while (x > 0) {
    ret = ret + sum[x];
    x = x - lowbit(x);
  }
  return ret;
}

using std::cin;
using std::cout;

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> m, tx = n, ty = n;
  for (int i = 1; i <= n; i++) cin >> x[i] >> y[i], ax[i] = x[i], ay[i] = y[i];
  for (int i = 1, l, r; i <= m; i++) {
    cin >> q[i].a >> q[i].b >> q[i].c >> q[i].d;
    ax[++tx] = q[i].a, ay[++ty] = q[i].b, ax[++tx] = q[i].c, ay[++ty] = q[i].d;
  }
  std::sort(ax + 1, ax + tx + 1), std::sort(ay + 1, ay + ty + 1);
  tx = std::unique(ax + 1, ax + tx + 1) - ax - 1;
  ty = std::unique(ay + 1, ay + ty + 1) - ay - 1;
  for (int i = 1; i <= n; i++) {
    x[i] = std::lower_bound(ax + 1, ax + tx + 1, x[i]) - ax;
    y[i] = std::lower_bound(ay + 1, ay + ty + 1, y[i]) - ay;
    op[++tot] = ope(0, x[i], y[i], i);  // add a point
  }
  for (int i = 1; i <= m; i++) {
    q[i].a = std::lower_bound(ax + 1, ax + tx + 1, q[i].a) - ax;
    q[i].b = std::lower_bound(ay + 1, ay + ty + 1, q[i].b) - ay;
    q[i].c = std::lower_bound(ax + 1, ax + tx + 1, q[i].c) - ax;
    q[i].d = std::lower_bound(ay + 1, ay + ty + 1, q[i].d) - ay;
    op[++tot] = ope(1, q[i].c, q[i].d, i);  // add a differenced query
    op[++tot] = ope(1, q[i].a - 1, q[i].b - 1, i);
    op[++tot] = ope(2, q[i].a - 1, q[i].d, i);
    op[++tot] = ope(2, q[i].c, q[i].b - 1, i);
  }
  std::sort(op + 1, op + tot + 1);  // sort by x, processing point additions first
  for (int i = 1; i <= tot; i++) {
    if (op[i].type == 0)
      add(op[i].y, 1);
    else if (op[i].type == 1)
      ans[op[i].id] += getsum(op[i].y);
    else
      ans[op[i].id] -= getsum(op[i].y);
  }
  for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
  return 0;
}
Luogu P1908 Inversion Pairs

Inversion counting can also be viewed as a scanline. Enumerate positions ii from right to left and count the points in [i+1,n][i+1,n] whose values lie in [0,ai][0,a_i]. Since the values can be as large as 10910^9, first coordinate-compress them. While scanning from right to left, update the Fenwick tree (or segment tree), then count how many values are smaller than the current one; this count is the number of inversions contributed by the current position.

Code
#include <algorithm>
#include <iostream>
using ll = long long;
using namespace std;

struct node {
  ll data;
  ll num;
} f[500010];

ll n, ans, a[500010];

bool cmp(node a, node b) {
  if (a.data == b.data) {
    return a.num < b.num;
  }
  return a.data < b.data;
}

ll sum[500010];

int lowbit(int x) { return x & (-x); }

void add(int x, int k) {
  while (x <= n) {
    sum[x] = sum[x] + k;
    x = x + lowbit(x);
  }
}

int getsum(int x) {
  int ret = 0;
  while (x > 0) {
    ret = ret + sum[x];
    x = x - lowbit(x);
  }
  return ret;
}

int main() {
  cin >> n;
  for (ll i = 1; i <= n; i++) {
    cin >> f[i].data;
    f[i].num = i;
  }
  sort(f + 1, f + 1 + n, cmp);
  for (int i = 1; i <= n; i++) {
    a[f[i].num] = i;
  }
  for (ll i = n; i > 0; i--) {
    ans += getsum(a[i]);
    add(a[i], 1);
  }
  cout << ans;
  return 0;
}
[Luogu P1972] [SDOI2009] HH's Necklace

Brief statement: given a sequence, answer how many distinct values occur in each queried interval [l,r][l,r].

Derive the property and enumerate all right endpoints with a scanline, maintaining the answer for every left endpoint in a data structure. Equivalently, turn it into a rectangle query in the plane.

Let aia_i be the previous occurrence position of preipre_i, or aia_i if prei=0pre_i = 0 has not appeared. Repeated occurrences contribute only once; assign the contribution to the first occurrence in the interval. The total contribution is therefore the number of indices with prex≤l−1pre_x \le l - 1.

The problem becomes: given prepre, answer how many indices in [l,r][l,r] satisfy prei≤l−1pre_i \le l - 1.

Regard preipre_i as a point whose horizontal coordinate is ii and whose vertical coordinate is preipre_i. The query asks for the number of points in the rectangle with lower-left corner (l,0)(l,0) and upper-right corner (r,l−1)(r,l - 1).

This query supports differences. Subtract the rectangle from (0,0)(0,0) to (r,l−1)(r,l - 1) from the rectangle from (0,0)(0,0) to (l−1,l−1)(l - 1,l - 1), which makes the scanline approach convenient.

Each update costs O(log⁡n)O(\log n). There are nn point additions and 2m2m query events, so total complexity is O((n+m)log⁡n)O((n + m) \log n).

Code
#include <algorithm>
#include <iostream>

int n, m, a[1000010], ans[1000010];
int pre[1000010], lst[1000010];  // maintain previous occurrences

struct ope {
  int type, x, y, id;

  ope(int type = 0, int x = 0, int y = 0, int id = 0) {
    this->type = type, this->x = x, this->y = y, this->id = id;
  }

  bool operator<(const ope& rhs) const {
    if (x == rhs.x) return type < rhs.type;
    return x < rhs.x;
  }
};

ope op[2500010];
int tot;  // total number of events

int sum[1000010];  // Fenwick tree

int lowbit(int x) { return x & (-x); }

void add(int x, int k) {
  x++;  // position 0 is updated too, so Fenwick indices are shifted by 1
  while (x <= n) {
    sum[x] = sum[x] + k;
    x = x + lowbit(x);
  }
}

int getsum(int x) {
  x++;
  int ret = 0;
  while (x > 0) {
    ret = ret + sum[x];
    x = x - lowbit(x);
  }
  return ret;
}

using std::cin;
using std::cout;

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    pre[i] = lst[a[i]], lst[a[i]] = i;  // maintain previous occurrences
    op[++tot] = ope{0, i, pre[i], i};   // add a point
  }
  cin >> m;
  for (int i = 1, l, r; i <= m; i++) {
    cin >> l >> r;
    op[++tot] = ope{1, r, l - 1, i};  // add a differenced query
    op[++tot] = ope{2, l - 1, l - 1, i};
  }
  std::sort(op + 1, op + tot + 1);  // sort by x, processing point additions first
  for (int i = 1; i <= tot; i++) {
    if (op[i].type == 0)
      add(op[i].y, 1);
    else if (op[i].type == 1)
      ans[op[i].id] += getsum(op[i].y);
    else
      ans[op[i].id] -= getsum(op[i].y);
  }
  for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
  return 0;
}

Exercises

In short, two-dimensional point counting maintains one dimension with a data structure and enumerates the other.

References

This content comes from OI Wiki: Scanline. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.