P4097 【模板】李超线段树 / [HEOI2013] Segment 题解

题意

有一个平面直角坐标系,总共 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 lkr 的一次函数中,找到在 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;
}
  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值