题意
有一个平面直角坐标系,总共 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 | 1double 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) { // 标记永久化,所以一路上需要不断地和自己的值取 maxif (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;
}