李超线段树
李超线段树
李超线段树是线段树的一个变种,主要支持在平面直角坐标系中动态地插入线段或直线,并查询某一条平行于 \(y\) 轴的直线与所有直线或线段的交点的纵坐标的最值。
假设只插入直线并且维护最大值:
在李超线段树中,对于每一个结点,我们需要维护的是在这个结点所对应的区间 \([l, r]\) 的中点处取值最大的直线。
假设我们现在有两条直线 \(y_1 = 3x + 5, y_2 = 2x + 6\),当前结点对应的区间为 \([1, 4]\):
那么,我们就比较这两条直线在 \(x = 2\) 时的取值:
图中绿色直线是直线 \(y = 3x + 5\),蓝色直线是 \(y = 2x + 6\),很显然的,当 \(x = 2\) 时,直线 \(y = 3x + 5\) 更优。
因此,区间 \([1, 4]\) 所对应的直线就是 \(y = 3x + 5\)。
那么 \(y = 2x + 6\) 应该怎么办呢?
这时,我们考虑比较这两条直线的斜率,我们会发现 \(y = 2x + 6\) 的斜率更小一些,也就是意味着,当 \(x > 2\) 时,直线 \(y = 3x + 5\) 的取值一定比 \(y = 2x + 6\) 更大,因此,我们只会用这条直线更新左区间。
但是需要注意的是当两条直线斜率相同时,就可以直接退出了。
有时候为了防止一些小数的情况,李超线段树通常我会写成左闭右开的形式。
并且由于坐标的范围问题,通常我会写动态开点。
ll F(Node x, int pos) {
return 1ll * x.k * pos + x.b;
}
void add(Node x) {
int i = 1, l = -INF, r = INF + 1;
while (l + 1 <= r) {
if (tr[i].b == inf) {
tr[i] = x; break;
}
int mid = l + (r - l) / 2;
if (F(tr[i], mid) > F(x, mid)) swap(tr[i], x);
if (l + 1 == r || tr[i].k == x.k) break;
if (tr[i].k > x.k) {
if (!son[i][1]) son[i][1] = ++c, tr[c] = {0, inf};
i = son[i][1], l = mid;
} else {
if (!son[i][0]) son[i][0] = ++c, tr[c] = {0, inf};
i = son[i][0], r = mid;
}
}
}
ll query(int x) {
int i = 1, l = -INF, r = INF + 1;
ll tp = inf;
while (l + 1 <= r && i) {
int mid = l + (r - l) / 2;
tp = min(tp, F(tr[i], x));
if (l + 1 == r) break;
if (x < mid) i = son[i][0], r = mid;
else i = son[i][1], l = mid;
}
return tp;
}
而如果是插入线段的话,事实上就是先找到线段所覆盖的区间,然后和直线一样更新线段树上的直线即可。
这里最需要注意的就是左闭右开区间的边界判断问题。
void modify(int i, int l, int r, int ql, int qr, Node x) {
if (qr <= l || ql >= r) return ;
if (ql <= l && r <= qr) {
add(x, i, l, r);
return ;
}
if (l + 1 == r) return ;
int mid = l + (r - l) / 2;
if (!son[i][0]) son[i][0] = ++c, tr[c] = {0, inf};
modify(son[i][0], l, mid, ql, qr, x);
if (!son[i][1]) son[i][1] = ++c, tr[c] = {0, inf};
modify(son[i][1], mid, r, ql, qr, x);
}