Address
https://www.lydsy.com/JudgeOnline/problem.php?id=1568
Solution
首先将问题简化:
一个二维平面,每次加入一条直线,在
处的横坐标为
且斜率为
,或求在
处线段的最高点的
坐标。
我们考虑线段树,线段树每个节点维护一条线段,这样询问时指需要从根走到对应的叶子节点,在走的过程中取
即可。
而插入直线时,容易发现我们要讨论的关键问题就是处理直线相交。
有一个巧妙的方法:
假设我们在节点
对应的区间
内插入线段
时,发现节点
内已经有线段
。
分类讨论:
(1)
在
下方:这时候
在节点
对应的区间里一定不是最高点。停止操作。
(2)
在
上方:这时候
在节点
对应的区间里一定不是最高点。用
替换
并停止操作。
(3)
和
交点的横坐标为
。
设
。
①如果
:保留
和
两条线段在
范围内靠上的线段,并把另一条线段推到
的左子节点,往左子节点继续执行一样的操作。
②如果
:保留
和
两条线段在
范围内靠上的线段,并把另一条线段推到
的右子节点,往右子节点继续执行一样的操作。
③否则
,执行①或②均可。
复杂度
。
线段树上的这种操作是 ZJ 省的 OI 界神犇李超提出的,
因此这被称为「李超树」或「李超线段树」「超哥线段树」。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define p2 p << 1
#define p3 p << 1 | 1
using namespace std;
const int N = 2e5 + 5;
int n;
double fr[N], to[N];
char s[N];
void ins(int l, int r, double up, double dn, int p) {
if (l == r) return (void) (fr[p] = to[p] = max(fr[p], up));
if (up <= fr[p] && dn <= to[p]) return;
if (up >= fr[p] && dn >= to[p])
return (void) (fr[p] = up, to[p] = dn);
int mid = l + r >> 1;
double k1 = 1.0 * (dn - up) / (r - l),
k2 = 1.0 * (to[p] - fr[p]) / (r - l);
double b1 = up - k1 * l, b2 = fr[p] - k2 * l;
double ist = (b1 - b2) / (k2 - k1);
if ((ist <= mid) ^ (up > fr[p]))
swap(up, fr[p]), swap(dn, to[p]), swap(k1, k2);
if (ist <= mid) ins(l, mid, up, up + k1 * (mid - l), p2);
else ins(mid + 1, r, up + k1 * (mid + 1 - l), dn, p3);
}
double query(int l, int r, int pos, int p) {
if (l == r) return fr[p];
double res = fr[p] + (to[p] - fr[p]) * (pos - l) / (r - l);
int mid = l + r >> 1;
if (pos <= mid) res = max(res, query(l, mid, pos, p2));
else res = max(res, query(mid + 1, r, pos, p3));
return res;
}
int main() {
double le, ri; int x;
cin >> n;
while (n--) {
scanf("%s", s);
if (s[0] == 'P') scanf("%lf%lf", &le, &ri),
ins(1, 50000, le, le + ri * 49999, 1);
else scanf("%d", &x),
printf("%d\n", (int) (query(1, 50000, x, 1) / 100));
}
return 0;
}