李超樹詳解
最近寫了幾棵李超樹,算是線段樹的擴展應用吧,順便在這里講講。
概念:
李超樹是一種高效的維護線段,單點查詢端點最大值的一種線段樹。支持插入一條線段,單點查詢這個點的權值最大值(即包含這個點中所有線段的\(y\)的最大值)。
具體實現:
我們先將每一條線段都表示成點斜式,接下來用\(k\)表示斜率,\(b\)表示截距。當我們插入一條線段\(y = k x + b\)的到區間\([l,r]\)(插入直線則是\([-inf,inf]\))時候,我們需要判斷這條線段是否可以更新這個這個區間的答案。我們記一條線段\(s\)為優勢線段,表示在這個區間\([l, r]\)中的線段中,\(s\)在\(mid = (l + r) >> 1\)這個點上的\(y\)的值是最大的。那么插入一條線段的時候,就會出現下面幾種情況:
- 當這個區間還沒有優勢線段的時候,就可以直接將該線段設成該區間的優勢線段,然后返回。
- 當這個區間已經有優勢線段,如果插入線段在區間\([l, r]\)的值都比該優勢線段大,那么就可以直接替換掉這個優勢線段,然后返回。或者是在區間\([l, r]\)的都比該優勢線段小,那么就可以直接返回了。
- 當這個區間的優勢線段\(seg\)和插入線段\(s\)存在某個交點的時候,顯然,我們需要更新這個區間的子區間的優勢線段的答案。我們假設交點位置為\(pos\),該區間中點位置為\(mid\),\(y_{seg_l} , y_{seg_r}\)表示\(seg\)線段左右兩個端點的\(y\)值,\(y_{s_l}, y_{s_r}\)同理。如果\(y_{seg_l} < y_{s_l},y_{seg_r}>y_{s_r}\),那么說明在\(pos\)右邊為\(seg\)優,\(pos\)左邊為\(s\)優,然后判斷此時\(pos\)的位置,如果此時\(pos\)的位置在\(mid\)的左邊,說明\(s\)這條優勢線段仍然需要下方到子區間去,然后繼續遞歸下去即可,另一半也是類似的。最后不要忘記更改本區間的優勢線段就行了。
查詢的話就比較簡單了,像普通的線段樹一樣,如果當前區間在查詢區間當中的話,那么就直接返回當前優勢線段,否則遞歸處理,然后順便和當前區間優勢線段的\(y_{seg_{pos}}\)比較一下,返回值更加大的線段就行了。
復雜度證明:
查詢操作不用多講,是\(O(log(n))\)的,然后具體的就是插入的操作會達到\(O(log^2(n))\)。因為尋找需要插入的區間需要\(log(n)\),然后一個區間的標記下方也需要\(O(log(n))\)的時間,所以總的復雜度也是\(O(nlog^2(n))\)的。
例題(SDOI2016 游戲)
題目傳送門
大意:給出一個\(n\)個節點帶邊權的樹,每個節點初始有一個值\(inf\),要求支持這些操作:
1. 選擇一條路徑\(s, t\),給路徑上的每個點\(u\)加上\(dis[s][u] * a + b\)的數字。
2. 選擇一條路徑\(s, t\),詢問路徑中所有數字的最小值。
題解:
維護路徑就不用說了,直接上樹剖就行了。我們將路徑分成\(s \to Lca\)和\(Lca \to t\)兩條路徑,發現每個點加上的數字實際上是一條直線,然后我們就可以用李超樹來維護這些直線了。總復雜度為\(O(nlog^3(n))\)的,信仰就行了。似乎用全局平衡二叉樹就可以優化成\(O(nlog^2(n))\)了?不過那都是樹剖的事了……
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 500;
const long long inf = 123456789123456789;
typedef long long ll;
int n, m, tot, clck;
int head[N], dfn[N], fa[N], top[N], idx[N], pos[N], heav[N], sz[N], dep[N], hav[N << 2];
ll dis[N], res[N << 2];
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
void read(int &x) {
x = 0;int f = 1;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
x *= f;
}
void read(ll &x) {
x = 0;int f = 1;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
x *= f;
}
struct edge {
int to, nxt, w;
}E[N << 2];
struct seg {
ll k, b;
ll F(ll x) {
return k * x + b;
}
void Print() {
cerr << k << " " << b << endl;
}
};
seg s[N << 2];
void Addedge(int u, int v, int w) {
E[++tot].to = v; E[tot].nxt = head[u]; head[u] = tot; E[tot].w = w;
E[++tot].to = u; E[tot].nxt = head[v]; head[v] = tot; E[tot].w = w;
}
void Dfs1(int o, int f, int deep, ll Dis) {
fa[o] = f; sz[o] = 1; dep[o] = deep; dis[o] = Dis;
int Mx = -1;
for(int i = head[o]; ~i; i = E[i].nxt) {
int to = E[i].to;
if(to == f) continue;
Dfs1(to, o, deep + 1, Dis + E[i].w);
sz[o] += sz[to];
if(sz[to] > Mx) {
Mx = sz[to];
heav[o] = to;
}
}
}
void Dfs2(int o, int tp) {
top[o] = tp; pos[dfn[o] = ++clck] = o;
if(!heav[o]) return ;
Dfs2(heav[o], tp);
for(int i = head[o]; ~i; i = E[i].nxt) {
int to = E[i].to;
if(!dfn[to]) Dfs2(to, to);
}
}
int GetLca(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x, y);
return y;
}
void Update(int o) {
res[o] = min(res[o], min(res[ls(o)], res[rs(o)]));
}
void Change(int o, int l, int r, seg nw) {
if(!hav[o]) return (void) (hav[o] = 1, s[o] = nw);
ll l1 = nw.F(dis[pos[l]]), r1 = nw.F(dis[pos[r]]), l2 = s[o].F(dis[pos[l]]), r2 = s[o].F(dis[pos[r]]);
if(l1 >= l2 && r1 >= r2) return ;
if(l2 >= l1 && r2 >= r1) return (void) (s[o] = nw, res[o] = min(res[o], min(l1, r1)));
int mid = (l + r) >> 1;
double pos0 = (double)(nw.b - s[o].b) / (double)(s[o].k - nw.k);
double mddis = (double)dis[pos[mid]];
if(pos0 <= mddis) Change(ls(o), l, mid, r2 >= r1 ? s[o] : nw);
else Change(rs(o), mid + 1, r, l2 >= l1 ? s[o] : nw);
if((pos0 <= mddis && r2 >= r1) || (pos0 > mddis && l2 >= l1)) s[o] = nw;
res[o] = min(res[o], min(l1, r1));
}
void Insert(int o, int L, int R, int l, int r, seg nw) {
if(l <= L && R <= r) return (void) (Change(o, L, R, nw));
int Mid = (L + R) >> 1;
if(Mid >= l) Insert(ls(o), L, Mid, l, r, nw);
if(Mid < r) Insert(rs(o), Mid + 1, R, l, r, nw);
Update(o);
}
ll Query(int o, int L, int R, int l, int r) {
if(l <= L && R <= r) return res[o];
ll ans = inf;
if(hav[o]) {
ll ret = min(s[o].F(dis[pos[max(l, L)]]), s[o].F(dis[pos[min(r, R)]]));
ans = min(ans, ret);
}
int Mid = (L + R) >> 1;
if(Mid >= l) ans = min(ans, Query(ls(o), L, Mid, l, r));
if(Mid < r) ans = min(ans, Query(rs(o), Mid + 1, R, l, r));
return ans;
}
void Modify(int x, int y, seg nw) {
while(top[x] != top[y]) {
Insert(1, 1, n, dfn[top[x]], dfn[x], nw);
x = fa[top[x]];
}
Insert(1, 1, n, dfn[y], dfn[x], nw);
}
ll Ask(int x, int y) {
ll ans = inf;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
ans = min(ans, Query(1, 1, n, dfn[top[x]], dfn[x]));
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x, y);
ans = min(ans, Query(1, 1, n, dfn[y], dfn[x]));
return ans;
}
int main() {
memset(head, -1, sizeof head);
read(n); read(m);
for(int i = 1, u, v, w; i < n; i++) {
read(u); read(v); read(w);
Addedge(u, v, w);
}
Dfs1(1, 0, 1, 0); Dfs2(1, 1);
seg Inf = (seg) {0, inf};
for(int i = 1; i <= n * 4; i++) res[i] = inf, s[i] = Inf, hav[i] = 1;
for(int i = 1, tp; i <= m; i++) {
read(tp);
if(tp == 1) {
int s, t;
ll a, b;
read(s); read(t); read(a); read(b);
int Lca = GetLca(s, t);
seg S1 = (seg) {-a, b + (ll)dis[s] * a};
seg S2 = (seg) {a, b + (ll)(dis[s] - 2 * dis[Lca]) * a};
Modify(s, Lca, S1);
Modify(t, Lca, S2);
}
else {
int s, t;
read(s); read(t);
printf("%lld\n", Ask(s, t));
}
}
return 0;
}