李超线段树
用处
插入若干条直线,求 \(x=a\) 这条直线与插入的直线的交点的最小/大纵坐标。
插入
以维护最小值为例。
记 \(cal_{u,a}\) 表示把 \(x=a\) 带入直线 \(u\) 的值。
插入一条直线,考虑对一个线段树上的区间维护一个 \(tag\) 表示当前区间最优秀的直线是 \(tag_k\)(这里的优秀指的是带入 \(x=mid\) 的值最小的直线)。
插入一条直线 \(u\),这个区间上的 \(tag\) 是 \(v\)。
当前区间对应的是 \([l,r]\)。
比较一下 \(cal_{u,mid}\) 与 \(cal_{v,mid}\) 的大小。
-
\(cal_{u,mid}\) 更小,直接把这个区间的 \(tag\) 改成 \(u\)。比较一下 \(cal_{u,l}\) 与 \(cal_{v,l}\) 的大小:如果 \(cal_{v,l}\) 更小,那么说明在区间 \([l,mid]\) 中,直线 \(v\) 仍然有可能成为最优直线,向区间 \([l,mid]\) 递归;否则再比较一下 \(cal_{u,r}\) 与 \(cal_{v,r}\),如果 \(cal_{v,r}\) 小,直线 \(v\) 就有可能在区间 \([mid+1,r]\) 中成为最优直线,向区间 \([mid + 1,r]\) 递归。显然两个条件要么满足一个,要么都不满足,所以最多递归 \(\log\) 次。(这里画一下图就很好理解了)
-
\(cal_{v,mid}\) 更小,处理方式和上述差不多。
// 存直线的
struct line {
int k, b;
} l[N];
// 查询值
int cal( int id, int x) {
return l[id].k * x + l[id].b;
}
// 插入一条直线 u
void insert( int & k, int l, int r, int u) {
if (! k) return k = ++ tot, tag[k] = u, void();
int & v = tag[k];
if (cal(v, mid) > cal(u, mid)) swap(v, u);
if (cal(u, l) < cal(v, l)) insert(ls[k], l, mid, u);
if (cal(u, r) < cal(v, r)) insert(rs[k], mid + 1, r, u);
}
询问
询问 \(x=a\) 这条直线与插入的直线的交点纵坐标最小值。
因为标记无法下传,所以考虑像标记永久化那样的询问处理方式。
对于包含了 \(x=a\) 这条直线的 \(\log\) 个区间,每个都取一下最小值即可。
int ask( int k, int l, int r, int x) {
if (! k) return inf;
return min(cal(tag[k], x), x <= mid ? ask(ls[k], l, mid, x) : ask(rs[k], mid + 1, r, x));
}
合并
如果想和并两棵李超线段树,很容易。
例如把树 \(b\) 合并进树 \(a\)。
直接在两颗树对应的区间上,把 \(b\) 的直线全部插入进 \(a\) 即可。
// 把 b 中的所有直线合并进 a
void merge( int & a, int b, int l, int r) {
if (! a) return a = b, void();
if (! b) return ;
insert(a, l, r, tag[b]); // 这里直接插入
if (l == r) return ;
merge(ls[a], ls[b], l, mid);
merge(rs[a], rs[b], mid + 1, r);
}
扩展
如果说插入的不是直线而是一条线段,该怎么办?
插入一条线段 \(((x1,y1),(x2,y2))\),可以看作插入一个有定义域为 \([x1,x2]\) 的直线。
那么在线段树上,如果当前区间 \([l,r]\) 被 \([x1,x2]\) 包含,那么在 \([l,r]\) 插入这条直线即可。
这样的复杂度 \(\log^2\)。

浙公网安备 33010602011771号