「數據結構」李超線段樹


#0.0 屑在前面

李超線段樹 由學軍中學隊爺李超在省選講課中提出。

事實上,整體來看並沒有什么特別特別的,只是線段樹維護的信息特殊化了。

#1.0 概述

#1.1 適用問題

支持動態維護一個平面直角坐標系,支持插入直線/線段,查詢與直線 \(x=x_0\) 的直線/線段交點縱坐標最大/最小的直線。

#1.2 大致思想

維護每個區間中,完全通過該區間,且位於最上層長度最長的直線,利用標記永久化思想。

考慮插入一條直線,且處理到了某個區間,那么可能有以下幾種情況:

  • 當前區間沒有被任何一條線段覆蓋,直接修改;
  • 根據端點值判斷新的線段是否完全被原本線段覆蓋,直接返回;
  • 根據端點值判斷新的線段是否完全覆蓋原本線段,直接修改,然后返回;
  • 通過交點位置與端點值的大小關系判斷長度關系,將長的記錄,短的遞歸進入相應子樹;

查詢時就是標記永久化的思想,將所經過的每一條被記錄的線段都拿出來比較即可。

綜上,查詢的時間復雜度是 \(O(\log n)\).

事實上,對於要插入的線段,我們先將其能覆蓋的區間通過線段樹划分為 \(O(\log n)\) 個,每個完全覆蓋的區間再單獨進行上面的操作,上面單獨操作時,每次線段長度至少減半,於是最多向下遞歸 \(O(\log n)\) 層,於是修改總體時間復雜度為 \(O(\log^2n)\).

#2.0 應用

#2.1 板子

P4254 [JSOI2008]Blue Mary開公司

由於插入的是直線而不是線段,於是不需要先分段,可以直接進行修改。

#define ll long long
#define db double

const int N = 200010;
const int LMT = 50010;
const int INF = 0x3fffffff;

template <typename T> void read(T &x) {
    int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

template <typename T> inline T Max(T x, T y) {return x > y ? x : y;}
template <typename T> inline T Min(T x, T y) {return x < y ? x : y;}

struct Node {int ls, rs, val;};
struct Line {
    double k, b;
    inline Line() {k = b = 0;}
    inline Line(double _k, double _b) {k = _k, b = _b;}
    inline double val(int x) {return k * x + b;}
} s[N];

struct LCTree {
    Node p[N]; int cnt, rt;

    inline LCTree() {cnt = rt = 0;}

    void build(int &k, int l, int r) {
        if (!k) k = ++ cnt; if (l == r) return;
        int mid = l + r >> 1; p[k].val = 0;
        build(p[k].ls, l, mid); build(p[k].rs, mid + 1, r);
    }   

    void insert(int k, int l, int r, int id) {
        if (!p[k].val) {p[k].val = id; return;}
        int mid = l + r >> 1;
        db l2 = s[p[k].val].val(l), r2 = s[p[k].val].val(r);
        db l1 = s[id].val(l), r1 = s[id].val(r);
        if (l1 <= l2 && r1 <= r2) return;
        if (l1 > l2 && r1 > r2) {p[k].val = id; return;}
        db x = (s[id].b - s[p[k].val].b) / (s[p[k].val].k - s[id].k);
        if (l1 > l2) {
            if (x > mid) insert(p[k].rs, mid + 1, r, p[k].val), p[k].val = id;
            else insert(p[k].ls, l, mid, id);
        } else {
            if (x > mid) insert(p[k].rs, mid + 1, r, id);
            else insert(p[k].ls, l, mid, p[k].val), p[k].val = id;
        }
    } 
    
    double query(int k, int l, int r, int x) {
        if (l == r) return s[p[k].val].val(x);
        int mid = l + r >> 1; double res = s[p[k].val].val(x);
        if (x <= mid) return Max(query(p[k].ls, l, mid, x), res);
        else return Max(query(p[k].rs, mid + 1, r, x), res);
    }
} t;

int n, lcnt, T; char op[N];

inline void Main() {
    scanf("%s", op);
    if (op[0] == 'P') {
        double k = 0, b = 0; scanf("%lf%lf", &b, &k);
        s[++ lcnt] = Line(k, b - k); t.insert(t.rt, 1, LMT, lcnt);
    } else {
        int x = 0; read(x);
        printf("%lld\n", (ll)(t.query(t.rt, 1, LMT, x) / 100.0));
    }
}

int main() {t.build(t.rt, 1, LMT); read(T); while (T --) Main(); return 0;}

#2.2 斜率優化

「NOI2007」貨幣兌換

\(f_i\) 表示到第 \(i\) 天可以擁有的最大錢數,先寫出一個大概的轉移方程

\[f_i=\max\limits_{0<j<i}\{num_A\cdot a_i+num_B\cdot b_i\}, \]

其中 \(num_A\)\(num_B\) 分別表示持有的 \(A\) 金卷的數量與 \(B\) 金卷的數量,這兩個數由 \(j\) 決定。顯然,第 \(j\) 天買入時,能得到的金卷比例是一定的,於是當天買入時擁有的錢數越大越好,也就是 \(f_j\),應當有

\[\begin{aligned} f_j&=num_A\cdot a_j+num_B\cdot b_j\\ f_j&=num_B\cdot R_j\cdot a_j+num_B\cdot b_j\\ num_B&=\dfrac{f_j}{R_j\cdot a_j+b_j}, \end{aligned} \]

同理可得

\[num_A=\dfrac{f_j\cdot R_j}{R_j\cdot a_j+b_j}, \]

於是我們可以寫出完整的狀態轉移方程

\[f_i=\max\limits_{0<j<i}\left\{\dfrac{f_j\cdot R_j}{R_j\cdot a_j+b_j}\cdot a_i+\dfrac{f_j}{R_j\cdot a_j+b_j}\cdot b_i\right\},\\ \frac{f_i}{b_i}=\max\limits_{0<j<i}\left\{\dfrac{f_j\cdot R_j}{R_j\cdot a_j+b_j}\cdot \frac{a_i}{b_i}+\dfrac{f_j}{R_j\cdot a_j+b_j}\right\}, \]

於是我們就可以直接將一個決策看作一條斜率為 \(\frac{f_j\cdot R_j}{R_j\cdot a_j+b_j}\)、縱軸截距為 \(\frac{f_j}{R_j\cdot a_j+b_j}\) 的直線,對於 \(i\),我們要求的就是已有的所有決策直線與 \(x=\frac{a_i}{b_i}\) 交點縱坐標的最大值。於是就可以直接用李超線段樹進行維護。

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM