1. 程式人生 > >【題解】HDU1542 Atlantis & 洛谷P1502 窗口的星星

【題解】HDU1542 Atlantis & 洛谷P1502 窗口的星星

amp 人的 iostream algo 使用 xpl printf clas string

  今天初步的學習了一下有關掃描線的相關知識。由於本人的做題量還不夠大,理解也並不很深刻,所以這篇文章還是留給自己看吧~ 掃描線,顧名思義就是用一根線在一個平面上掃描,掃到線段 / 矩形的時候就將其所含有的信息從數據結構中刪去 / 加入數據結構。

  通過這兩道題目,可以大致的感受到掃描線的作用與神奇之處。

  T1.HDU 1542 Atlantis

  求多個矩形的面積並。我們可以維護一條與x軸平行的掃描線,不斷移動掃描線,在遇到一條新的邊的時候就將掃描線與邊的高度差 * 掃描線上被邊覆蓋的長度加到答案中(可以畫圖感受一下)。問題轉化為:如何統計掃描線上被邊覆蓋的長度?我們可以讓線段樹上的節點 \(l\) 表示 \(id[l]\) 到 \(id[r]\) 這一段區間。那麽對於一段管轄 \(l --> r\) 的區間,我們可以維護兩個值:一個值是這個區間被覆蓋的次數(完整覆蓋),另一個則是這個區間一共被覆蓋的長度。如果這個區間被完整覆蓋過,顯然一共被覆蓋的長度就是區間的總長。但倘若沒有,那麽就繼承兒子的信息。這樣就可以統計出掃描線上被覆蓋的長度了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define maxn 100000
#define db double
int T, n, cnt;
db ans, cal[maxn], sum[maxn], id[maxn];

struct node
{
    db x1, x2, y1, y2; int id;
    node(db X1 = 0, db Y1 = 0
, db X2 = 0, db Y2 = 0) { x1 = X1, y1 = Y1, x2 = X2, y2 = Y2; } friend bool operator <(const node& a, const node& b) { return a.x1 < b.x1; } }P[maxn], X[maxn]; struct node2 { int x1, x2, k; db h; node2(int X1 = 0, int X2 = 0, db H = 0, int K = 0) { x1 = X1, x2 = X2, h = H, k = K; } friend
bool operator <(const node2& a, const node2& b) { return a.h < b.h; } }L[maxn]; void Update(int p, int l, int r, int L, int R, int x) { if(L <= l && R >= r) { cal[p] += x; if(cal[p]) sum[p] = id[r + 1] - id[l]; else sum[p] = sum[p << 1] + sum[p << 1 | 1]; return; } if(L > r || R < l) return; int mid = (l + r) >> 1; Update(p << 1, l, mid, L, R, x); Update(p << 1 | 1, mid + 1, r, L, R, x); if(cal[p]) sum[p] = id[r + 1] - id[l]; else sum[p] = sum[p << 1] + sum[p << 1 | 1]; } void init() { memset(sum, 0, sizeof(sum)); memset(cal, 0, sizeof(cal)); } int main() { while(scanf("%d", &n), n != 0) { int tot = 2 * n; init(); cnt = 0, ans = 0; for(int i = 1; i <= n; i ++) { db x1, x2, y1, y2; scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2); P[i] = node(x1, y1, x2, y2); X[i] = node(x1, 0, 0); X[i + n] = node(x2, 0, 1); X[i].id = X[i + n].id = i; } sort(X + 1, X + 1 + tot); X[0].x1 = -1; for(int i = 1; i <= tot; i ++) { if(X[i].x1 != X[i - 1].x1) id[++ cnt] = X[i].x1; if(!X[i].x2) P[X[i].id].x1 = cnt; else P[X[i].id].x2 = cnt; } for(int i = 1; i <= n; i ++) { L[i] = node2(P[i].x1, P[i].x2, P[i].y1, 1); L[i + n] = node2(P[i].x1, P[i].x2, P[i].y2, -1); } sort(L + 1, L + 1 + tot); for(int i = 1; i <= tot; i ++) { if(i != 1) ans += (L[i].h - L[i - 1].h) * sum[1]; Update(1, 1, cnt, L[i].x1, L[i].x2 - 1, L[i].k); } printf("Test case #%d\nTotal explored area: %.2f\n\n", ++ T, ans); } return 0; }

  T2.洛谷P1502 窗口的星星

  求在一個矩形中點權的最大值。我們可以發現一件事:將窗口的左下角位於一顆星星的地方一定是最優的。那麽我們可以定義一顆星星的管轄範圍為一個以它為左下角的,長為 \(W\),寬為 \(H\) 的矩形。這樣就擁有了一個很好的性質:如果兩個矩形有交點,就說明這兩個節點可以同時出現在窗口中。這樣我們可以使用一條掃描線從左往右掃過整個平面,讓線段樹中存儲的矩形均為左豎線與掃描線距離在 \(W\) 之內的矩形。接下來這其中的矩形均為可以水平相交的矩形,接下來只要用線段樹解決豎直方向相交的問題了。我們在線段樹上定義一個節點為可以覆蓋到這個節點的所有矩形的權值之和,答案就是所有節點中的最大值。

#include <bits/stdc++.h>
using namespace std;
#define maxn 450000
#define int long long
int n, W, H, mark[maxn], mx[maxn];
int tot, cnt, ans;

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < 0 || c > 9) { if(c == -) k = -1; c = getchar(); }
    while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar();
    return x * k;
}

struct node
{
    int x, y, k;
    friend bool operator <(const node& a, const node& b)
    { return a.x == b.x ? a.y < b.y : a.x < b.x; }
}P[maxn], X[maxn];

struct node2
{
    int l, r, h, k;
    node2(int L = 0, int H = 0, int K = 0) { l = L, h = H, k = K; }
    friend bool operator <(const node2& a, const node2& b) 
    { return a.h == b.h ? a.k > b.k : a.h < b.h; }
}L[maxn];

void Push_down(int p)
{
    if(!mark[p]) return;
    mark[p << 1] += mark[p], mark[p << 1 | 1] += mark[p];
    mx[p << 1] += mark[p], mx[p << 1 | 1] += mark[p];
    mark[p] = 0;
}

void Update(int p, int l, int r, int L, int R, int k)
{
    if(L <= l && R >= r) 
    {
        mark[p] += k, mx[p] += k;
        return;
    }
    if(L > r || R < l) return;
    Push_down(p);
    int mid = (l + r) >> 1;
    Update(p << 1, l, mid, L, R, k); 
    Update(p << 1 | 1, mid + 1, r, L, R, k);
    mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
}

void init()
{
    memset(mx, 0, sizeof(mx));
    memset(mark, 0, sizeof(mark));
    memset(L, 0, sizeof(L));
}

signed main()
{
    int T = read();
    while(T --)
    {
        init(); ans = -1;
        n = read(), W = read(), H = read();
        for(int i = 1; i <= n; i ++)
        {
            P[i].x = read(), P[i].y = read(), P[i].k = read();
            X[i].x = P[i].x, X[i].k = i;
            X[i + n].x = P[i].x + W - 1, X[i + n].k = i;
        }
        tot = 2 * n, cnt = 0; 
        sort(X + 1, X + 1 + tot); X[0].x = -1;
        for(int i = 1; i <= tot; i ++)
        {
            if(X[i].x != X[i - 1].x) cnt ++;
            if(!L[X[i].k].l) L[X[i].k] = node2(cnt, P[X[i].k].y, P[X[i].k].k);
            else L[X[i].k].r = cnt;
        }
        for(int i = 1; i <= n; i ++)
        {
            L[i + n] = L[i], L[i + n].k = -L[i].k;
            L[i + n].h = L[i].h + H - 1; 
        }
        sort(L + 1, L + 1 + tot);
        for(int i = 1; i <= tot; i ++)
        {
            Update(1, 1, tot, L[i].l, L[i].r, L[i].k);
            ans = max(ans, mx[1]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

【題解】HDU1542 Atlantis & 洛谷P1502 窗口的星星