資料結構——李超線段樹 學習筆記
維護直線
考慮線段樹維護區間最優線段。
-
其中,最優線段指的是,在區間 \([l,r]\) 中,中點 \(mid\) 處最優的線段。
-
我們稱一個線段在單點更優 / 最優,顯然,是指此處的函式值更大。
-
我們下面稱一個線段在區間內更優 / 最優,是指在中點處的比較。
-
我們稱一個線段在區間 / 單點嚴格更優,是指在該區間任何一處都更優。
插入直線
首先考慮在區間 \([l,r]\) 中插入線段:
-
若該區間無線段,那麼直接讓他成為最優線段。
-
如果已經有了,注意到我們不方便將一個區間下傳,因此標記永久化。
設新線段為 \(f\),當前的最優線段為 \(g\),考慮合併,
- 我們欽定 \(f\) 在區間 \([l,r]\) 弱於 \(g\),如果不滿足那麼交換即可。
-
若在左右端點 \(f\) 都更弱,那麼 \(g\) 嚴格優於 \(f\),\(f\) 不可能成為答案,不需要下傳。
-
若在左端點 \(f\) 更優,因為 \(f\) 在中點更弱,因此左側一定存在分界點,遞迴左側。
-
若在右端點 \(f\) 更優,因為 \(f\) 在中點更弱,因此右側一定存在分界點,遞迴右側。
複雜度分析:
-
因為兩直線最多隻有一個交點,因此左右最多遞迴一個。
-
因此,時間複雜度為,單次 \(\mathcal O(n\log n)\)。
為何不能欽定 \(f\) 強於 \(g\) 然後加入 \(f\) 捏?
注意到如果 \(f\) 嚴格強於 \(g\),那麼為了更新答案,我們還是需要交換 \(f,g\)。
然後這樣這個問題相當於沒有。
查詢最值
標記永久化之後,我們需要把從根到葉子節點的每一個最優線段計算。
注意到是區最值,是可以重複的,那麼我們隨便搞就可以了。
時間複雜度同線段樹,為單次 \(\mathcal O(\log n)\)。
程式碼
P4254 [JSOI2008] Blue Mary 開公司
struct line {
double k, b;
} p[M];
int tot;
double calc(int u, int t) {
return p[u].b + p[u].k * t;
}
#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)
int best[N << 2];
void modify(int k, int l, int r, int u) {
int &v = best[k];
int mid = (l + r) >> 1;
if (calc(u, mid) > calc(v, mid)) swap(u, v);
if (calc(u, l) > calc(v, l)) modify(ls(k), l, mid, u);
if (calc(u, r) > calc(v, r)) modify(rs(k), mid + 1, r, u);
}
double query(int k, int l, int r, int t) {
double res = calc(best[k], t);
if (l == r) return res;
int mid = (l + r) >> 1;
if (t <= mid) res = max(res, query(ls(k), l, mid, t));
else res = max(res, query(rs(k), mid + 1, r, t));
return res;
}
void Insert(double k, double b) {
p[++tot] = {k, b};
modify(1, 1, (int)5e4, tot);
}
double Query(int t) {
return query(1, 1, (int)5e4, t);
}
維護線段
插入線段
我們延續上面的思路,但是。
我們需要線段樹式的遍歷到每一個節點,才能更新最優線段。
-
注意到線段樹會把區間分為 \(\mathcal O(\log n)\) 個區間,
-
我們需要對每個區間進行 \(\mathcal O(\log n)\) 的更新,
-
因此,總時間複雜度是 \(\mathcal O(\log^2n)\) 的。
查詢最值
和上面沒有變化。
程式碼
下面是和上面類似的程式碼,也很好寫。
struct line {
double k, b;
} p[M];
int tot;
double calc(int u, int t) {
return p[u].b + p[u].k * t;
}
#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)
int best[N << 2];
void update(int k, int l, int r, int u) {
int &v = best[k];
int mid = (l + r) >> 1;
if (calc(u, mid) > calc(v, mid)) swap(u, v);
if (calc(u, l) > calc(v, l)) update(ls(k), l, mid, u);
if (calc(u, r) > calc(v, r)) update(rs(k), mid + 1, r, u);
}
void modify(int k, int l, int r, int p, int q, int u) {
if (l >= p && r <= q) return update(k, l, r, u);
int mid = (l + r) >> 1;
if (q <= mid) return modify(ls(k), l, mid, p, q, u);
if (p >= mid + 1) return modify(rs(k), mid + 1, r, p, q, u);
modify(ls(k), l, mid, p, q, u), modify(rs(k), mid + 1, r, p, q, u);
}
double query(int k, int l, int r, int t) {
double res = calc(best[k], t);
if (l == r) return res;
int mid = (l + r) >> 1;
if (t <= mid) res = max(res, query(ls(k), l, mid, t));
else res = max(res, query(rs(k), mid + 1, r, t));
return res;
}
void Insert(double k, double b) {
p[++tot] = {k, b};
modify(1, 1, (int)5e4, tot);
}
double Query(int t) {
return query(1, 1, (int)5e4, t);
}
P4097 【模板】李超線段樹 / [HEOI2013] Segment
這個題目強制線上,且需要輸出最優線段且編號最小,因此可能會被卡精度,
constexpr double eps = 1e-8;
inline int Cmp(double x, double y) {
if (x - y > eps) return 1;
if (y - x > eps) return -1;
return 0;
}
inline pair<double, int> Max(const pair<double, int> &a,
const pair<double, int> &b) {
int c = Cmp(a.first, b.first);
if (c == 0) return a.second < b.second ? a : b;
return c == 1 ? a : b;
}