题意
有一个平面直角坐标系,总共 n n n 个操作,每个操作有两种:
- 给定正整数 x 0 , y 0 , x 1 , y 1 x_0,y_0,x_1,y_1 x0,y0,x1,y1 表示一条线段的两个端点。你需要在平面上加入这一条线段,第 i i i 条被插入的线段的标号为 i i i。
- 给定正整数 k k k,问与直线 x = k x=k x=k 相交的线段中,交点的纵坐标最大的线段的编号。
解法
需要用到线段树的一个变体:李超线段树。我们将操作转化一下:
- 加入一个一次函数,定义域为 [ l , r ] [l,r] [l,r](这个一次函数画出的线段,左端点横坐标为 l l l,右端点横坐标为 r r r);
- 给定 k k k,在所有 l ≤ k ≤ r l\le k\le r l≤k≤r 的一次函数中,找到在 x = k x=k x=k 处取值最大的那个函数(意思就是在横坐标为 k k k、垂直于 y y y 轴的直线上,找到 y y y 坐标最高的交点)。
来看一个例子:
因为我们总是取 y y y 坐标最高的交点作为答案,所以真正取到答案的部分长这样:
- 图中黑色部分即为答案。
那如何在线段树上维护这个东西呢?我们考虑线段树上被两条线段完全覆盖的一个部分为 [ l , r ] [l,r] [l,r] 的节点的情况:
- 图中红蓝色为两个一次函数的图像、橙色为对答案有贡献的部分、深灰色为 m i d = ⌊ l + r 2 ⌋ mid=\lfloor\cfrac{l+r}{2}\rfloor mid=⌊2l+r⌋、浅灰色为转折点。李超线段树的每个节点都会维护当前区间中优势最大的线段(图中红色的线段),因而李超线段树需要标记永久化。
其中,红色线段(记为 g g g)先被加入,显然整个线段在当时就是最优的;蓝色线段(记为 f f f)然后被加入。此时深红色的部分没有受到影响, g g g 仍然优势最大;但浅蓝色部分答案改变。我们将其分为两个子区间(而 m i d mid mid 左右的区间另称为左/右区间),可以发现一定有一个子区间被左或右区间完全包含(浅蓝色被右区间包含),即在两条线段中,肯定有一条线段只可能成为左或右区间的答案( f f f 只可能成为右区间最优的线段)。
我们不妨令,在 m i d mid mid 处 f f f 不如 g g g 优(反之交换两条线段即可),则:
- 若在左端点处 f f f 更优,那么 f f f 和 g g g 会在左半区间中产生交点, f f f 只有在左区间才可能优于 g g g,于是递归到左儿子中进行下传;
- 反之,若在右端点处 f f f 更优,那么交点在右半区间中产生,递归到右儿子进行下传(图中的情况);
- 如果左右端点 g g g 都更优,那么 f f f 不可能成为答案,不需要下传(即 g g g 整个在 f f f 的上方)。
当交点正好就在 m i d mid mid 上时,我们直接将其归入 m i d mid mid 上 f f f 不如 g g g 优的情况。这样我们就完成了对完全覆盖的区间的修改。对于其他部分覆盖的区间直接递归左右儿子解决即可。对于查询操作,将自己的答案与左右儿子取个 min \min min 返回。
因为每次修改操作都需要递归左右儿子,所以加线段的复杂度是 O ( log 2 n ) O(\log^2n) O(log2n) 的。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 40000;
struct Line {
double k,b;
} L[maxn]; int cnt;
void addLine(int x0,int y0,int x1,int y1) {
// 加入一条线段
if (x0 == x1) L[++ cnt] = Line {0,max(y0,y1) * 1.0};
else {
double X = x1 - x0, Y = y1 - y0;
L[++ cnt].k = Y / X, L[cnt].b = y0 - Y / X * x0;
}
}
const double eps = 1e-9;
namespace LiChaoSegmentTree {
#define lson l,mid,rt << 1
#define rson mid + 1,r,rt << 1 | 1
double K(int u,int d) {
return L[u].b + L[u].k * d;
}
int check(double X,double Y) {
return X - Y > eps ? 1 : Y - X > eps ? -1 : 0;
}
int ans[(maxm << 2) + 5];
void color(int l,int r,int rt,int u) {
int mid = l + r >> 1;
int cM = check(K(u,mid), K(ans[rt],mid)); // 计算中点谁占优势
if (cM == 1 || (cM == 0 && u < ans[rt])) // 总是令新线段不如旧线段优
swap(u,ans[rt]); // 把自己更新好
int cL = check(K(u,l),K(ans[rt],l));
int cR = check(K(u,r),K(ans[rt],r));
// 选择新线段可能更新的区域递归
if (cL == 1 || (cL == 0 && u < ans[rt])) color(lson,u);
if (cR == 1 || (cR == 0 && u < ans[rt])) color(rson,u);
}
int nowl,nowr;
void modify(int l,int r,int rt,int u) {
if (nowl <= l && r <= nowr)
return color(l,r,rt,u);
int mid = l + r >> 1;
if (nowl <= mid) modify(lson,u);
if (mid < nowr) modify(rson,u);
}
struct Answer { // 返回的答案
int id; double val;
bool operator<(const Answer &oth) const {
int c = check(val,oth.val);
return c == -1 ? 1 : c == 1 ? 0 : id > oth.id;
}
Answer(int X = 0,double Y = 0.0) { id = X, val = Y; }
};
int now;
Answer query(int l,int r,int rt) { // 标记永久化,所以一路上需要不断地和自己的值取 max
if (l == r) return Answer{ans[rt],K(ans[rt],now)};
int mid = l + r >> 1; Answer res(ans[rt],K(ans[rt],now));
if (now <= mid) return max(query(lson),res);
else return max(query(rson),res);
}
} using namespace LiChaoSegmentTree;
int n,tmp;
const int P = 39989, Q = 1e9;
int chg(int X,int p) {
return (X + tmp - 1 + p) % p + 1;
}
int main() {
scanf("%d",&n);
for (int i = 1,op,x0,x1,y0,y1,x;i <= n;i ++) {
scanf("%d",&op);
if (op == 1) {
scanf("%d%d%d%d",&x0,&y0,&x1,&y1);
x0 = chg(x0,P), x1 = chg(x1,P), y0 = chg(y0,Q), y1 = chg(y1,Q);
if (x0 > x1) swap(x0,x1), swap(y0,y1);
nowl = x0, nowr = x1;
addLine(x0,y0,x1,y1);
modify(1,maxm,1,cnt);
} else {
scanf("%d",&x), x = now = chg(x,P);
printf("%d\n",tmp = query(1,maxm,1).id);
}
}
return 0;
}