zoukankan      html  css  js  c++  java
  • 李超线段树浅谈

    前言

    李超线段树是线段树的一个变种,支持在平面直角坐标系中动态插入线段,查询一条竖线与所有线段的交点纵坐标的最大值或最小值。

    引入

    下面以 【HEOI2013】Segment 为例讲解李超线段树。

    题目大意:

    要求在平面直角坐标系下维护两个操作:

    1. 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)
    2. 给定一个数 \(k\),询问与直线 \(l:x=k\) 相交的线段中,交点纵坐标最大的线段的编号。

    对于线段树上每一个结点,维护一个区间最优线段。当一条线段满足以下条件时,它才能成为最优线段:

    • 该线段的定义域覆盖整个区间。
    • 该线段在区间中点处的值最大。

    令区间 \([L,R]\) 当前最优线段为 \(l'\) ,考虑如何在区间 \([L,R]\) 中插入线段 \(l\)

    • 若当前区间没有最优线段,或者线段 \(l′\) 被线段 \(l\) 完全覆盖(对于这道题,指线段 \(l′\) 完全在插入线段下),那么线段 \(l\) 直接成为当前区间的最优线段。
    • 若线段 \(l\) 被线段 \(l′\) 完全覆盖 一转攻势,那么线段 \(l\) 就没用了,不用再向下递归。
    • 否则,线段 \(l\) 与线段 \(l'\) 相交。令区间中点为 \(mid\) ,比较线段 \(l\) 和线段 \(l'\) 在中点 \(mid\) 处的纵坐标大小,更新当前区间最优线段。

    对于第三种情况,虽然线段 \(l'\) 在当前区间中不再是最优线段了,但是它依然可能成为子区间的最优线段,代码实现中是交换线段 \(l\) 和线段 \(l'\),用线段 \(l'\) 更新子区间信息。

    考虑如何更新子结点信息。令用来更新子区间信息的线段为 \(l''\)(因为上面更新过最优线段,所以线段 \(l''\) 一定是在 \(mid\) 处纵坐标较小的线段),对交点位置分类讨论:

    • 若交点在 \(mid\) 左侧,如图:wxnOCF.png

      红色线段为当前最优线段,橙色线段为线段 \(l''\),此时线段 \(l''\) 只有可能在左子区间中成为最优线段,所以只需在线段树上更新左儿子的信息。

    • 若交点在 \(mid\) 右侧,同理,更新右儿子信息。

    • 若交点在 \(mid\) 处,若线段 \(l''\)\(L\) 处的纵坐标较大,更新左儿子,否则更新右儿子。

    查询时在线段树上二分找到这个位置,比较途径的所有区间的最优线段在这个位置时的纵坐标,取最大值即可。这其实是标记永久化的思想。

    代码:

    用结构体封装一条线段:

    struct line
    {
        double k,b;// 斜率
        int id;    // 部分题需要记录线段编号
        line(double k=0,double b=0,int id=0):k(k),b(b),id(id){}
        inline double calcu(int pos)       // 计算线段在pos位置的纵坐标
        {
        return k*pos+b;
        }
        inline double cross(const line &T) // 求两条线段的交点横坐标
        {
        return (T.b-b)/(k-T.k);
         }
    };
    

    线段树结点:

    struct node
    {
        int l,r;   // 区间左右端点
        line L;    // 最优线段
        bool flag; // 记录区间内是否有最优线段
        node(int l,int r,line L,bool flag):l(l),r(r),L(L),flag(flag){}
        node(){}
    }tree[maxn<<2];
    #define ls (p<<1)   // 左儿子
    #define rs (p<<1|1) // 右儿子
    

    建树:

    inline void build(int p,int l,int r)
    {
        tree[p]=node(l,r,line(),false);
        // 新建一个没有最优线段的结点
        if(l==r) return;
        int mid=(l+r)>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
    }
    

    最重要的 modify 操作:

    这里并没有计算出交点横坐标,因为交点位置可以通过比较线段在端点处的纵坐标得出:

    最优线段在 \(mid\) 处的取值一定较大,如果插入线段在左端点的取值较大,那么交点在左子区间,否则在右子区间。

    这样计算同时也避免了多讨论交点在 \(mid\) 处的情况。

    inline void modify(int p,int L,int R,line ln)
    {
        int l=tree[p].l,r=tree[p].r;
        if(L<=l&&r<=R) // 如果插入线段的定义域覆盖整个区间
        {
        double lp=tree[p].L.calcu(l),rp=tree[p].L.calcu(r);
        double lq=ln.calcu(l),rq=ln.calcu(r);
            // 计算两条线段在区间端点的纵坐标
        if(!tree[p].flag) tree[p].L=ln,tree[p].flag=true;
        else if(lq-lp>eps&&rq-rp>eps) tree[p].L=ln;
            // 没有最优线段或者完全覆盖
        else if(lq-lp>eps||rq-rp>eps) // 相交
        {
        int mid=(l+r)>>1;
        if(ln.calcu(mid)-tree[p].L.calcu(mid)>eps) swap(tree[p].L,ln);
        if(ln.calcu(l)>tree[p].L.calcu(l))
        modify(ls,L,R,ln);
        else modify(rs,L,R,ln);
        }
        }
        else // 若未覆盖整个区间,检查子区间
        {
        int mid=(l+r)>>1;
        if(L<=mid) modify(ls,L,R,ln);
        if(R>mid) modify(rs,L,R,ln);
        }
    }
    

    查询:

    typedef pair<double,int> pii;
    inline pii query(int p,int pos) // 本题还要返回线段编号
    {
        int l=tree[p].l,r=tree[p].r;
        double ans=tree[p].L.calcu(pos);
        int id=tree[p].L.id;
        if(l==r) return make_pair(ans,id);
        int mid=(l+r)>>1;
        if(pos<=mid)
        {
        pii lq=query(ls,pos);
        if(lq.first>ans||(fabs(lq.first-ans)<eps&&lq.second<id))
        ans=lq.first,id=lq.second;
        }
        else
        {
        pii rq=query(rs,pos);
        if(rq.first>ans||(fabs(rq.first-ans)<eps&&rq.second<id))
        ans=rq.first,id=rq.second;
        }
        return make_pair(ans,id);
    }
    

    复杂度分析

    每次修改将区间分成 \(\log n\) 个子区间,每个子区间的最优线段最多下放 \(\log n\) 层,所以修改复杂度为 \(O(\log n)\)

    每次查询经过 \(\log n\) 个结点,复杂度 \(O(\log n)\)

    参考资料

    本文作者:AFewMoon,文章地址:https://www.cnblogs.com/AFewMoon/p/15502067.html

    知识共享许可协议

    本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

    限于本人水平,如果文章有表述不当之处,还请不吝赐教。

  • 相关阅读:
    element 步骤条steps 点击事件
    element-ui的rules中正则表达式
    从master分支创建自己的分支
    2.1 系统调用io实现原理
    2-3形参和实参
    2-2函数
    2-1.编译和链接
    linux高编信号-------setitimer()、getitimer()
    linux高编IO-------有限状态机编程原理(mycpy)
    linux高编线程-------线程同步-条件变量
  • 原文地址:https://www.cnblogs.com/AFewMoon/p/15502067.html
Copyright © 2011-2022 走看看