李超樹詳解


李超樹詳解

最近寫了幾棵李超樹,算是線段樹的擴展應用吧,順便在這里講講。

概念:

李超樹是一種高效的維護線段,單點查詢端點最大值的一種線段樹。支持插入一條線段,單點查詢這個點的權值最大值(即包含這個點中所有線段的\(y\)的最大值)。

具體實現:

我們先將每一條線段都表示成點斜式,接下來用\(k\)表示斜率,\(b\)表示截距。當我們插入一條線段\(y = k x + b\)的到區間\([l,r]\)(插入直線則是\([-inf,inf]\))時候,我們需要判斷這條線段是否可以更新這個這個區間的答案。我們記一條線段\(s\)為優勢線段,表示在這個區間\([l, r]\)中的線段中,\(s\)\(mid = (l + r) >> 1\)這個點上的\(y\)的值是最大的。那么插入一條線段的時候,就會出現下面幾種情況:

  1. 當這個區間還沒有優勢線段的時候,就可以直接將該線段設成該區間的優勢線段,然后返回。
  2. 當這個區間已經有優勢線段,如果插入線段在區間\([l, r]\)的值都比該優勢線段大,那么就可以直接替換掉這個優勢線段,然后返回。或者是在區間\([l, r]\)的都比該優勢線段小,那么就可以直接返回了。
  3. 當這個區間的優勢線段\(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;
}


免責聲明!

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



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