「李超线段树」
李超线段树
省选之前的知识了,现在省选苟进队后赶紧补一下
李超线段树是由李超发明的用于求函数定点最值线段树,又名李超树
例题
大意是,在一个二维平面上,依次加入若干条线段,询问对于某个 \(x\) 的最大值,强制在线
李超树像普通线段树一样同样支持两种操作:插入和查询
插入
在李超树上每个节点 \([l,r]\),存了一个优势线段,这条线段在 \(mid\) 的值是最大的
考虑在区间 \([s,t]\) 插入一条线段 \(l_0\),每到一个线段树上的节点,与节点上存的优势线段 \(l_1\) 比较
-
如果 \(l_0\) 在 \(x=mid\) 上的值比 \(l_1\) 大,显然这个节点的优势线段变成了 \(l_0\),\(swap\;l_0\) 和 \(l_1\)
-
如果 \(l_0\) 在 \(x=l\) 上的值比 \(l_1\) 大,要么 \(l_0\) 在 \([l,mid]\) 这段区间覆盖了 \(l_1\),或者 \(l_0\) 与 \(l_1\) 在 \([l,mid]\) 中有交点,递归到左区间
-
如果 \(l_0\) 在 \(x=r\) 上的值比 \(l_1\) 大,要么 \(l_0\) 在 \([mid + 1,r]\) 这段区间覆盖了 \(l_1\),或者 \(l_0\) 与 \(l_1\) 在 \([mid+1,r]\) 中有交点,递归到右区间
\(swap\;l_0\) 和 \(l_1\) 是因为,如果 \(l_0\) 成为了这个区间的优势线段,那么 \(l_1\) 也有可能与 \(l_0\) 从而递归到儿子区间
因为如果 \(l_0\) 与 \(l_1\) 有交,只会有一个交点,所以只会递归进一个儿子,单次插入复杂度是 \(\Theta (\log n)\) 的
查询
查询对于某个 \(x=k\) 的极值,在李超树上向下递归,用所有包含它的线段树区间的优势线段求出若干个值,一块取个 \(\max\) 就是答案
考虑为什么正确,首先我们插入一条线段时,当且仅当当前的优势线段完全覆盖了这个条线段,才会把这个线段舍去,所以不会影响答案的正确性
然后考虑为什么把路径上的所有值都求一遍,因为这个节点存的线段可能与儿子节点的线段有交点,到底取哪个线段作为最大值并不确定,所以把路径上的都求一遍取个最大值,跟标记永久化差不多
然后这个题就很好解决了,直接套用板子就行了
板子
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
const double EPS = 1e-8;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int T, n, m = 4e4, lastans;
int tree[maxn << 2];
struct Line {
double k, b;
Line () {}
Line (register double x, register double y) { k = x, b = y; }
} a[maxn];
inline double Calc (register Line a, register int x) {
return a.k * x + a.b;
}
inline void Modify (register int rt, register int l, register int r, register int s, register int t, register int x) { // 插入一条线段
register int mid = (l + r) >> 1;
if (s <= l && r <= t) {
if (Calc (a[x], mid) - Calc (a[tree[rt]], mid) > EPS) swap (tree[rt], x);
if (Calc (a[x], l) - Calc (a[tree[rt]], l) > EPS) Modify (rt << 1, l, mid, s, t, x);
if (Calc (a[x], r) - Calc (a[tree[rt]], r) > EPS) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
return;
}
if (s <= mid) Modify (rt << 1, l, mid, s, t, x);
if (t > mid) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
}
inline int Query (register int rt, register int l, register int r, register int x) { // 单点查询
if (l == r) return tree[rt];
register int mid = (l + r) >> 1;
if (x <= mid) {
register int tmp = Query (rt << 1, l, mid, x);
return Calc (a[tmp], x) - Calc (a[tree[rt]], x) > EPS ? tmp : tree[rt];
} else {
register int tmp = Query (rt << 1 | 1, mid + 1, r, x);
return Calc (a[tmp], x) - Calc (a[tree[rt]], x) > EPS ? tmp : tree[rt];
}
}
int main () {
T = read();
while (T --) {
register int opt = read();
if (! opt) {
register int x = (read() + lastans - 1) % 39989 + 1;
printf ("%d\n", lastans = Query (1, 1, m, x));
} else {
register int x0 = (read() + lastans - 1) % 39989 + 1, y0 = (read() + lastans - 1) % 1000000000 + 1;
register int x1 = (read() + lastans - 1) % 39989 + 1, y1 = (read() + lastans - 1) % 1000000000 + 1;
if (x0 > x1) swap (x0, x1), swap (y0, y1);
if (x0 == x1) a[++ n] = Line (0, max (y0, y1));
else a[++ n] = Line (1.0 * (y1 - y0) / (x1 - x0), y0 - 1.0 * (y1 - y0) / (x1 - x0) * x0);
Modify (1, 1, m, x0, x1, n);
}
}
return 0;
}
进阶
首先看到 \(a\times dis+b\),是一个一次函数的形式,不妨考虑先树剖一下,然后用李超树维护最大值
对于每次插入操作,先求出 \(s\) 和 \(t\) 的 \(lca\),预处理出每个点到根节点的距离 \(dis[u]\),然后化简一下式子
对于 \(s\) 到 \(lca\) 的那条路径上的点 \(u\),增加的贡献是
对于 \(t\) 到 \(lca\) 的那条路径上的点,增加的贡献是
然后就可以直接插入了
现在问题就剩如果维护区间最小值,容易发现对于一个区间内的线段,最小值无非就是从两个端点的地方取到,所以线段树上再维护一个区间最小值,每次 \(Pushup\),最后区间查询的时候,经过的区间上存的优势线段也能为答案作出贡献,算上就好了
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 2e5 + 50;
const ll INF = 123456789123456789;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int n, m;
struct Line {
int k;
ll b;
Line () {}
Line (register int x, register ll y) { k = x, b = y; }
};
struct Tree {
Line seg;
ll val;
Tree () { seg.k = 0, seg.b = val = INF; }
} tree[maxn << 2];
inline ll Calc (register Line a, register ll x) { return a.k * x + a.b; }
struct Edge {
int to, next, w;
} e[maxn << 1];
int tot, head[maxn];
inline void Add (register int u, register int v, register int w) {
e[++ tot].to = v;
e[tot].w = w;
e[tot].next = head[u];
head[u] = tot;
}
int deep[maxn], size[maxn], son[maxn], f[maxn];
int tic, top[maxn], dfn[maxn], rk[maxn];
ll dis[maxn];
inline void DFS0 (register int u, register int fa) {
deep[u] = deep[fa] + 1, size[u] = 1;
for (register int i = head[u]; i; i = e[i].next) {
register int v = e[i].to;
if (v == fa) continue;
dis[v] = dis[u] + e[i].w, f[v] = u, DFS0 (v, u), size[u] += size[v];
if (size[son[u]] < size[v]) son[u] = v;
}
}
inline void DFS1 (register int u, register int t) {
top[u] = t, dfn[u] = ++ tic, rk[tic] = u;
if (son[u]) DFS1 (son[u], t);
for (register int i = head[u]; i; i = e[i].next) {
register int v = e[i].to;
if (v == f[u] || v == son[u]) continue;
DFS1 (v, v);
}
}
inline int LCA (register int u, register int v) {
while (top[u] != top[v]) {
if (deep[top[u]] < deep[top[v]]) swap (u, v);
u = f[top[u]];
}
return deep[u] < deep[v] ? u : v;
}
inline void Pushup (register int rt, register int l, register int r) {
tree[rt].val = min (tree[rt].val, min (Calc (tree[rt].seg, dis[rk[l]]), Calc (tree[rt].seg, dis[rk[r]])));
tree[rt].val = min (tree[rt].val, min (tree[rt << 1].val, tree[rt << 1 | 1].val));
}
inline void Modify (register int rt, register int l, register int r, register int s, register int t, register Line x) {
register int mid = (l + r) >> 1;
if (s <= l && r <= t) {
if (Calc (x, dis[rk[mid]]) < Calc (tree[rt].seg, dis[rk[mid]])) swap (tree[rt].seg, x);
if (Calc (x, dis[rk[l]]) < Calc (tree[rt].seg, dis[rk[l]])) Modify (rt << 1, l, mid, s, t, x);
if (Calc (x, dis[rk[r]]) < Calc (tree[rt].seg, dis[rk[r]])) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
return Pushup (rt, l, r), void ();
}
if (s <= mid) Modify (rt << 1, l, mid, s, t, x);
if (t > mid) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
Pushup (rt, l, r);
}
inline ll Query (register int rt, register int l, register int r, register int s, register int t) {
if (s <= l && r <= t) return tree[rt].val;
register int mid = (l + r) >> 1;
register ll ans = min (Calc (tree[rt].seg, dis[rk[max (l, s)]]), Calc (tree[rt].seg, dis[rk[min (r, t)]]));
if (s <= mid) ans = min (ans, Query (rt << 1, l, mid, s, t));
if (t > mid) ans = min (ans, Query (rt << 1 | 1, mid + 1, r, s, t));
return ans;
}
inline void TreeModify (register int u, register int v, register Line x) {
while (top[u] != top[v]) {
if (deep[top[u]] < deep[top[v]]) swap (u, v);
Modify (1, 1, n, dfn[top[u]], dfn[u], x), u = f[top[u]];
}
if (dfn[u] > dfn[v]) swap (u, v);
Modify (1, 1, n, dfn[u], dfn[v], x);
}
inline ll TreeQuery (register int u, register int v, register ll ans = INF) {
while (top[u] != top[v]) {
if (deep[top[u]] < deep[top[v]]) swap (u, v);
ans = min (ans, Query (1, 1, n, dfn[top[u]], dfn[u])), u = f[top[u]];
}
if (dfn[u] > dfn[v]) swap (u, v);
return min (ans, Query (1, 1, n, dfn[u], dfn[v]));
}
int main () {
n = read(), m = read();
for (register int i = 1, u, v, w; i <= n - 1; i ++)
u = read(), v = read(), w = read(), Add (u, v, w), Add (v, u, w);
DFS0 (1, 0), DFS1 (1, 1);
while (m --) {
register int opt = read(), u = read(), v = read();
if (opt == 1) {
register int a = read(), b = read(), lca = LCA (u, v);
TreeModify (u, lca, Line (- a, a * dis[u] + b));
TreeModify (lca, v, Line (a, a * dis[u] - 2 * a * dis[lca] + b));
} else printf ("%lld\n", TreeQuery (u, v));
}
return 0;
}