【恐怖の演算法】 掃描線

余温辞發表於2024-12-03

【恐怖の演算法】 掃描線

引入

掃描線一般運用在圖形上面,它和它的字面意思十分相似,就是一條線在整個圖上掃來掃去,它一般被用來解決圖形面積,周長,以及二維數點等問題。

二維矩形面積並問題

在二維座標系上,給出多個矩形的左下以及右上座標,求出所有矩形構成的圖形的面積。

過程

根據圖片可知總面積可以直接暴力即可求出面積,如果資料大了怎麼辦?這時就需要講到 掃描線 演算法。

現在假設我們有一根線,從下往上開始掃描:

掃描線示意圖

按這樣把矩形分成幾個部分,小矩形的高是掃過的距離。我們可以發現,小矩形的寬一直在變化。

給每一個矩形的上下邊進行標記,下面的邊標記為 1,上面的邊標記為 -1。每遇到一個水平邊時,讓這條邊(在橫軸投影區間)的權值加上這條邊的標記。

小矩形(不一定只有一個)的寬度就是整個數軸上權值大於 0 的區間總長度。

實現

很容易想到可以用線段樹。

用線段樹維護矩形的長,也就是整個數軸上覆蓋次數大於 0 的點。

需求列舉如下:

  • 一段區間權值加 1、減 1。

  • 統計整個數軸上,區間權值大於 0 的「區間長度和」。

如果你嘗試直接用普通線段樹模板來實現的話,也許會遇到些挫折。具體地,由於在區間加時,即使修改區間和節點管理區間重合,我們還是不能常數時間知道覆蓋次數如何變化。這是因為我們不能直接知道:管理範圍裡有多長的區間會從 1 變成 0(從 0 變成 1)。

這道題只需要樸素的分治就能實現:維護每個節點管理區間中「整體 修改的權值和 w[]」(類似不用下放的懶惰標記)和「覆蓋長度 v[]」兩個資訊。

需要用到離散化。

例題

題目傳送門:CZOJ #25.「模板」掃描線

分析

模版題,同理直接用線段樹

離散化+線段樹+模擬

由於資料量太大,我們先把所有的牆壁離散化,用線段樹維護每個離散化後的橫座標的最高點,然後模擬求出答案。

程式碼

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int n;
struct a {
    int x;
    int h;
    int v;
    int l;
} r[200005] = {};
int d[100005] = {};
int cmh(a x, a y) { return x.h < y.h; }
int cmx(a x, a y) { return x.x < y.x; }
int t[400005] = {};
int m = 0, ans[400005][2] = {};
void ad(int k, int x, int l, int r, int v) {
    int mid = (l + r) / 2;
    t[k] += v;
    if (l != r) {
        if (x <= mid) {
            ad(k * 2, x, l, mid, v);
        } else {
            ad(k * 2 + 1, x, mid + 1, r, v);
        }
    }
}
int rank(int k, int l, int r) {
    int mid = (l + r) / 2;
    if (t[k] == 0) {
        return 0;
    }
    if (l == r) {
        return mid;
    }
    if (t[k * 2 + 1] == 0) {
        rank(k * 2, l, mid);
    } else {
        rank(k * 2 + 1, mid + 1, r);
    }
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> r[i * 2].h >> r[i * 2 - 1].x >> r[i * 2].x;
        r[i * 2 - 1].h = r[i * 2].h;
        r[i * 2 - 1].v = 1;
        r[i * 2].v = -1;
    }
    sort(r + 1, r + 2 * n + 1, cmh);
    r[0].x = 1000000001;
    r[2 * n + 1].x = r[0].x;
    for (int i = 1; i <= 2 * n; i++) {
        r[i].l = r[i - 1].l + (r[i].h != r[i - 1].h);
        d[r[i].l] = r[i].h;
    }
    sort(r + 1, r + 2 * n + 1, cmx);
    int i = 1, j = 1;
    int now = 0, pas = 0;
    while (i <= 2 * n) {
        j = i;
        while (r[j].x == r[i].x) {
            ad(1, r[i].l, 1, 100000, r[i].v);
            now = rank(1, 1, 100000);
            i++;
        }
        if (pas != now) {
            m++;
            ans[m][0] = r[j].x;
            ans[m][1] = d[pas];
            m++;
            ans[m][0] = r[j].x;
            ans[m][1] = d[now];
            pas = now;
        }
    }
    ans[m][1] = 0;
    cout << m << endl;
    for (int i = 1; i <= m; i++) {
        cout << ans[i][0] << " " << ans[i][1] << endl;
    }
}

噩夢演算法の終結~

相關文章