李超线段树支持这样的操作:
- 插入一条直线。
- 询问与$x=x_0$相交的点的最大/小的纵坐标(接下来我们以最大值为例)
它基于线段树的标记永久化,也就是说,我们对于区间$[l,r]$维护$x=mid$上最高的直线的编号(但是如果$[l,r]$的所有点都以$[l,r]$的父亲记录的直线作为最大值,那么$[l,r]$的子树都没有记录的直线(这就是标记永久化)),然后询问操作肯定没有问题,重点就是添加直线了。
假设当前递归到$[l,r]$,要插入$k_1x+b_1$,之前记录的直线为$k_2x+b_2$。
不妨设$k_1<k_2$,$k_1\geq k_2$的情况同理。
第一种情况:$k_1mid+b_1>k_2mid+b_2$,那么更新$[l,r]$的标记,只向$[mid+1,r]$递归,因为$[l,mid]$上所有点都以$k_1x+b_1$作为最大值。
第二种情况:$k_1mid+b_1\leq k_2mid+b_2$,那么不更新$[l,r]$的标记,只向$[l,mid]$递归,因为$[mid+1,r]$上所有点都以$k_2x+b_2$作为最大值。
这样显然更新一次是$O(\log^2 n)$的。(我也不知道我在想什么。。。)
#include<bits/stdc++.h> #define Rint register int using namespace std; const int N = 100003, M = 50000; int n, tot; struct Line { double k, b; inline double val(int x){return k * (x - 1) + b;} } l[N]; int tag[M << 2]; inline double query(int x, int L, int R, int pos){ if(L == R) return l[tag[x]].val(L); int mid = L + R >> 1; double ans = l[tag[x]].val(pos); if(pos <= mid) ans = max(ans, query(x << 1, L, mid, pos)); else ans = max(ans, query(x << 1 | 1, mid + 1, R, pos)); return ans; } inline void insert(int x, int L, int R, int id){ if(L == R){ if(l[tag[x]].val(L) < l[id].val(L)) tag[x] = id; return; } int mid = L + R >> 1; if(l[tag[x]].k < l[id].k){ if(l[tag[x]].val(mid) < l[id].val(mid)){insert(x << 1, L, mid, tag[x]); tag[x] = id;} else insert(x << 1 | 1, mid + 1, R, id); } else { if(l[tag[x]].val(mid) < l[id].val(mid)){insert(x << 1 | 1, mid + 1, R, tag[x]); tag[x] = id;} else insert(x << 1, L, mid, id); } } int main(){ scanf("%d", &n); while(n --){ char s[10]; scanf("%s", s); if(s[0] == 'Q'){ int t; scanf("%d", &t); printf("%d\n", (int) query(1, 1, M, t) / 100); } else { ++ tot; scanf("%lf%lf", &l[tot].b, &l[tot].k); insert(1, 1, M, tot); } } }
例题:CF1175G(如果认为难度落差太大,本人不负责)