李超線段樹

這個利用了線段樹標記永久化的思想 , 支持查詢不少條直線 \(y=kx+b\) (線段)在 \(x=k\) 的最值 .html

經常能夠在一些最優化問題中 優化時間複雜度 , 加強程序效率 .算法

算法簡述

假設咱們當前維護最大值 (最小值同理) .優化

用線段樹維護每個區間的一個 優點線段 (暴露在最上面的線段 , 也就是不會被別的線段在這個區間徹底蓋住)this

對於網上那些說暴露最多的 , 他們程序都彷佛不能體現 , 故我的理解是這樣 .spa

能夠證實 , 對於 \(x=k\) 在這些直線上的最大值 , 確定是全部包含這個點的全部區間 優點線段 對應 \(y\) 的最大值 .code

暴露在最上面線段 是個抽象化的過程 , 咱們能夠把這個當作在 \([l, r]\) 區間中 \(\displaystyle f(mid = \frac{l + r}{2})\) 的最大的那條直線 .htm

咱們插入的時候討論四種狀況 qwqblog

  1. 這個區間原本沒有線段 , 直接放在這裏就好了 .
  2. 新的線段徹底被舊的線段蓋住 , 這種狀況直接退出就好了 .
  3. 新的線段把舊的線段徹底蓋住 , 直接修改而後退出就好了 .
  4. 在這個區間中有交點 , 先改 , 而後把劣勢的放入交點的那一側 .

這個提及來容易 , 但實現起來有點細節 .遞歸

咱們先比較 \(f(mid)\) 大小 , 若是新的大 , 那咱們先交換 , 也就是說 把當前較爲優點的先放在此處 , 劣勢的拿下來等待安排 .

而後咱們看新舊的交點 也就是 \(\displaystyle x= -\frac{b_1-b_2}{k_1-k_2}\) (注意 若是 \(k_1=k_2\) 要判斷掉 , 直接退出就好了)

\(x\) 若是不在此段區間內 , 那麼意味着優點的在這個區間都優於劣勢 , 那麼直接退出 , 不然 把當前劣勢的下方入標點那側繼續遞歸處理 .

有時候判斷交點不行 , 會錯掉 , 其實最好的是判斷斜率 .

由於有時候交點恰好在 \(mid\) 處時候 , 你可能會存在瞎走的狀況 .

這時候斜率能幫助你判斷接下來應該向哪裏走 , 也就是它接下來哪裏會最優 .

而後查詢的話 , 在線段樹上一直向下走 , 直到走入端點所處的區間 , 而後一路把存在的優點線段的 \(f(x)\)\(\max\) .

分析一波時間複雜度qwq ...

插入的時候每一個線段最多被分紅 \(O(\log n)\) 個區間 , 而後繼續下放也須要 \(O(\log n)\) 的複雜度 , 插入的時候複雜度就是 \(O(\log^2 n)\) . (若是插入直線的話就是 \(O(\log n)\) 的複雜度)

而後查詢的話和普通單點查詢的複雜度是同樣的 \(O(\log n)\) .

代碼實現

const int N = 5e4 + 1e3;

struct Line {
    int l, r, id; double k, b;

    Line (int xl = 0, int xr = 0, int yl = 0, int yr = 0, int id = 0) {
        this -> id = id; l = xl, r = xr;
        if (xl != xr) k = 1.0 * (yr - yl) / (xr - xl), b = yl - k * xl;
        else k = .0, b = max(yl, yr);
    }

    double func(int x) { return k * x + b; }
} ;

const double eps = 1e-8;
inline int Sgn(double x) { return (x > eps) - (x < -eps); }

inline bool Cmp(Line a, Line b, int x) { 
    if (!a.id) return true;
    int dir = Sgn(a.func(x) - b.func(x)); 
    return (dir != 0) ? dir < 0 : a.id < b.id; 
}

#define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
struct Chao_Segment_Tree {
    Line Adv[N << 2];

    void Down(int o, int l, int r, Line up) {
        int mid = (l + r) >> 1;
        if (Cmp(Adv[o], up, mid)) swap(Adv[o], up);
        if (l == r || Sgn(Adv[o].k - up.k) == 0) return ;

        double x = (Adv[o].b - up.b) / (up.k - Adv[o].k);
        if (x < l || x > r) return ;
        if (x <= mid) Down(lson, up); else Down(rson, up);
    }

    void Insert(int o, int l, int r, int ul, int ur, Line up) {
        if (ul <= l && r <= ur) { Down(o, l, r, up); return ; }
        int mid = (l + r) >> 1;
        if (ul <= mid) Insert(lson, ul, ur, up);
        if (ur > mid) Insert(rson, ul, ur, up);
    }

    Line Query(int o, int l, int r, int qp) {
        if (l == r) return Adv[o];
        int mid = (l + r) >> 1; Line tmp;
        tmp = (qp <= mid ? Query(lson, qp) : Query(rson, qp));
        return Cmp(Adv[o], tmp, qp) ? tmp : Adv[o];
    }
} T;

例題講解

  1. BZOJ 3165: [HEOI 2013]Segment

    要求在平面直角座標系下維護兩個操做:

    1. 在平面上加入一條線段。記第 \(i\) 條被插入的線段的標號爲 \(i\)
    2. 給定一個數 \(k\) ,詢問與直線 \(x = k\) 相交的線段中,交點最靠上的線段的編號。

    這個題就是模板題啦 qwq

    可是網上好多都像我這種 , 把線段基本上視做一條直線 , 從頂至底更新的時候 , 沒有判斷當前更新的線段 是否覆蓋了 \(x=k\) 這個範圍 就致使答案失真... (ps : 這個一拍就錯啦) 可是官方數據好像很神奇 居然過啦 qwq

    我太菜啦 , 懶得改啦 , 注意下這個問題就好了 ....

  2. Codeforces Round #463 F. Escape Through Leaf

    又來騙訪問啦 qwq 一道好題 2333

相關文章
相關標籤/搜索