1. 程式人生 > 實用技巧 >李超線段樹

李超線段樹

首先來看一道題:[HEOI2013]Segment

可以發現的是,實質上某個 \(x = k\) 處的最大值只有一個,因此我們需要儘可能減少計算不優的線段。

那麼對於兩條線段 \(a, b(a \ne b)\) 它們左右端點橫座標相同,就只會產生如下四種情況:

  • \(a\) 的兩端縱座標均比 \(b\) 的大,顯然 \(a\) 會完全覆蓋在 \(b\) 上面,因此 \(b\) 是無意義的。

  • \(b\) 的兩端縱座標均比 \(a\) 大,顯然 \(b\) 會完全覆蓋在 \(a\) 上面,因此 \(a\) 是無意義的。

  • \(a, b\) 交點在中點右側:如果 \(a.k > b.k\)

    ,那麼在中點左側顯然 \(a\) 會被 \(b\) 完全覆蓋,那麼顯然 \(a\) 對左半邊區間是不優的:否則 \(a\) 對於右邊邊區間不是最優的。

  • \(a, b\) 交點在中點左側:如果 \(a.k > b.k\),那麼在中點左側顯然 \(b\) 會被 \(a\) 完全覆蓋,那麼顯然 \(b\) 對左半邊區間是不優的:否則 \(b\) 對於右邊邊區間不是最優的。

從特殊情況出發,每次我們都插入一條 \([1, n]\) 的線段。

現在考慮插入一條線段 \(T\),如果當前區間內不存線上段,顯然 \(T\) 是最優的,直接插入然後返回。

如果當前區間存線上段,考慮上面的四種情況。

如果是前兩條情況,顯然不優的那條線段對全域性任意一個位置的貢獻沒有更優的那條直線優,因此我們只需要儲存更優的那條直線即可。

如果是後面兩種情況,以 \(a.k > b.k\) 兩條線段交點在中點左邊為例。

顯然此時在右半區間 \(b\) 會被 \(a\) 完全覆蓋,\(b\) 不會對右邊區間的查詢有任何貢獻。

\(b\) 可能對左半邊區間的查詢有貢獻,因此我們將 \(b\) 傳入左半邊區間進行遞迴操作。

可以發現,每條線段只保留在了其可能對整個區間造成貢獻的最上層區間,因此每個查詢時最優的那些線段一定會被保留下來。

那麼查詢的時候就要一路將經過的所有節點上的最優線段取 \(\max\)

,類似於標記永久化。

你會發現每次我們修改最多會往半邊區間走,每次查詢同樣也是一樣,因此這個複雜度是 \(O(n \log n)\) 的。

回到原題,原題當中要求每次對一個區間插入一條線段。

不難發現我們可以直接使用線段樹來維護這個東西,因為線段樹每次涉及修改的區間有 \(\log n\) 個,因此總複雜度是 \(O(n \log ^ 2 n)\) 的。

#include <bits/stdc++.h>
using namespace std;
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 100000 + 5;
const int P = 39989;
const int PP = 1e9;
const double eps = 1e-6;
struct tree { double k, b; int id;} t[N << 2];
int n, k, x, y, xx, yy, ans, tot, opt;
int read() {
    char c; int x = 0, f = 1;
    c = getchar();
    while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
double F(tree a, double x) { return a.k * x + a.b;}
tree chkmax(tree a, tree b, int x) { 
    if(fabs(F(a, x) - F(b, x)) < eps) return a.id < b.id ? a : b;
    else return F(a, x) < F(b, x) ? b : a;
}
void down(int p, int l, int r, tree k) {
    if(!t[p].id) t[p] = k; 
    else if(F(t[p], l) < F(k, l) && F(t[p], r) < F(k, r)) t[p] = k;
    else if(F(t[p], l) >= F(k, l) && F(t[p], r) >= F(k, r)) return;
    else {
        if(l == r) return;
        double Mid = 1.0 * (l + r) / 2;
        if(k.k > t[p].k) {
            if(F(k, Mid) > F(t[p], Mid)) down(ls, l, mid, t[p]), t[p] = k;
            else down(rs, mid + 1, r, k);
        }
        else {
            if(F(k, Mid) > F(t[p], Mid)) down(rs, mid + 1, r, t[p]), t[p] = k;
            else down(ls, l, mid, k);
        }
    }
}
void update(int p, int l, int r, int x, int y, tree k) {
    if(l >= x && r <= y) { down(p, l, r, k); return;}
    if(mid >= x) update(ls, l, mid, x, y, k);
    if(mid < y) update(rs, mid + 1, r, x, y, k);
}
tree query(int p, int l, int r, int x, int y) {
    tree ans = t[p];
    if(l == r) return ans;
    if(mid >= x) ans = chkmax(ans, query(ls, l, mid, x, y), x);
    if(mid < y) ans = chkmax(ans, query(rs, mid + 1, r, x, y), x);
    return ans;
}
int main() {
    n = read();
    while (n--) {
        opt = read();
        if(opt == 0) {
            k = read(), k = (k + ans - 1) % P + 1;
            printf("%d\n", ans = query(1, 1, P, k, k).id);
        }
        else {
            x = read(), y = read(), xx = read(), yy = read();
            x = (x + ans - 1) % P + 1, xx = (xx + ans - 1) % P + 1;
            y = (y + ans - 1) % PP + 1, yy = (yy + ans - 1) % PP + 1;
            if(x > xx) swap(x, xx), swap(y, yy);
            if(x == xx) update(1, 1, P, x, xx, (tree){0, max(y, yy), ++tot});
            else {
                double K = 1.0 * (yy - y) / (xx - x), B = y - K * x;
                update(1, 1, P, x, xx, (tree){K, B, ++tot});
            }
        }
    }
    return 0;
}

值得一提的是,這種涉及區間修改以及查詢(在某個值域區間範圍內也是一樣)的問題,往往可以先考慮所有操作都是整個區間的情況,而對區間進行操作往往可以使用線段樹多 \(1 \log\) 解決。