李超线段树

7 篇文章 1 订阅
2 篇文章 0 订阅

1 前言之前

感觉最近代码能力极其低下啊
而自己又在学新的东西
于是就有了苦果,代码总是出小错误,调不出来
然后就要花很多时间
看来还需提升思维缜密性

2 前言

李超线段树实在mangouyang的博客中看到的,看起来算法非常良心,算法并不是特别难,主要在于其思想
它有个经典问题
支持在线,有两种操作

  • 第一种操作:在平面中加入一条线段
  • 第二种操作:给定 a a a,求与 x = a x=a x=a直线相交的线段中相交点 y y y值最大/小的点的编号是多少(这篇博客我默认用最大来讲,因为有个例题[bzoj3165][heoi2013]Segment
    (所有输入都是整数)

3 思路

考虑维护一棵线段树,我们知道,线段树的一个节点代表着一个区间,我们另在这个节点上的值为一个线段的标号,用来存储当前区间的优势线段
定义一个区间的优势线段,指这条线段的 x x x坐标范围覆盖整个区间,并且在区间里面的某个位置上可能取到那个位置的最大值,如果包含某个位置的所有区间的优势线段中没有出现线段 a a a,那么 a a a一定不是查询这个位置的答案
我们考虑如何维护这个信息

3.1 插入操作
  • 如果只有一次操作该咋样?
    由于成为优势线段的前提是这条线段的 x x x坐标范围覆盖整个区间,那么我们就像写线段树的区间操作一样,进行区间赋值就好了,复杂度 Θ ( l o g n ) \Theta(logn) Θ(logn)
  • 多次操作
    考虑多次操作我们一样可以像一次操作一样做,但是这个时候会发现有个问题,如果某个区间我要进行赋值,但是它原本就有值,我不能直接覆盖上去,这该怎么办呢?
    这个时候就需要进行一些处理
3.2 add操作

定义add操作为加入一条线段更新某个区间的答案
相当于现在有两个线段 a a a b b b,现在他们要只留下一个,并且保持优势线段的性质
由于这是线段树,所以如果要维护信息,可以进行push_down的骚操作,但是两边都push_down的复杂度是不对的,会变成 Θ ( 区 间 长 度 ) \Theta(区间长度) Θ(),所以我们要减少push_down


首先我们考虑这样一件事情,如果在这段区间的每一个位置一条线段都在另一条线段的上方,那我们就直接保留上方的那条线段,不用push_down,因为下方的线段已经不可能成为答案了。特殊的,在区间长度为1的时候一定符合这个条件,所以长度为1的区间一定不用push_down

这张图中, s e g m e n t 1 segment1 segment1严格比 s e g m e n t 2 segment2 segment2优,所以显然 s e g m e n t 2 segment2 segment2可以舍去


如果不满足上述条件,那么两个线段一定在这个区间中有交点,也就是说两条线段都符合优势线段的条件,都要保留
一个区间不能保留两个值,所以一定要push_down
然后我们又发现:如果一个线段在某个区间中的贡献只在左半边或者是右半边,那么它就可以被push_down
容易发现,给出的两条线段,至少有一条满足条件,上图在这里插入图片描述
我们可以求出两条线段的交点,容易发现,在交点左边, s e g m e n t 2 segment2 segment2高,在交点右边, s e g m e n t 1 segment1 segment1高,由于交点在 m i d mid mid左边,那么我们就可以保留 s e g m e n t 1 segment1 segment1,将 s e g m e n t 2 segment2 segment2push_down到左儿子,其它情况同理
考虑复杂度,由于一条线段在线段树中有贡献的节点数是 Θ ( l o g n ) \Theta(logn) Θ(logn)的,一次贡献最多被push_down树高( Θ ( l o g n ) \Theta(logn) Θ(logn))次,总复杂度是 Θ ( l o g 2 n ) \Theta(log^2n) Θ(log2n)
至此,我们就解决了这个地方的问题

3.3 查询操作

给出一个坐标
由于我们维护了优势线段的的特征,所以单次查询就直接把logn个节点取最优即可

4 实现

考虑我们如何实现上述过程,我们只需要支持两个功能,一个是求一个线段在某个横坐标下的值,一个是线段求交,由于我给出的例题要求相同情况下取编号较小的,所以还要加 e p s eps eps的特判
我们只要求两条线段在两个端点的大小关系,如果相同说明是前面说的第一种情况,否则是第二种,第二种情况进行一次线段求交即可
此外还要特判斜率为 + inf ⁡ +\inf +inf的情况
(垃圾数据,没有这些特判照样通过此题)
代码不是很清真,贴出来吧

// luogu-judger-enable-o2
#include<cstdio>
#include<cctype>
#include<algorithm>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
    inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
    inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
    inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int MAX=131073;const double eps=1e-7;
int n,lastans;
struct segment
{
    int x0,y0,x1,y1;
    inline double calc(const int place)const
    {
        if(x0==x1)return y1;
        return (double)(y1-y0)/(double)(x1-x0)*(place-x0)+y0;
    }
}Q[100001];int tot;
inline double Cross(const segment&x,const segment&y)
{
    return (double)x.x0+(double)abs((double)x.y0-(double)y.calc(x.x0))/abs((double)(x.y1-x.y0)/(double)(x.x1-x.x0)-(double)(y.y1-y.y0)/(double)(y.x1-y.x0))+eps;
}
int l[MAX],r[MAX],mid[MAX],id[MAX];
inline void ini(const int root,const int ll,const int rr)
{
    l[root]=ll,r[root]=rr,mid[root]=(ll+rr)>>1;
    if(ll==rr)return;
    ini(root<<1,ll,mid[root]),ini(root<<1|1,mid[root]+1,rr);
}
inline void add(const int root,const int ins)
{
    if(!id[root])id[root]=ins;
    else if(l[root]==r[root])
    {
        const double v1=Q[ins].calc(l[root]),v2=Q[id[root]].calc(l[root]);
        if(abs(v1-v2)<eps)mind(id[root],ins);
        if(v1>v2)id[root]=ins;
    }
    else
    {
        const int u=id[root];
        const bool lsign=Q[ins].calc(l[root])<=Q[u].calc(l[root]),rsign=Q[ins].calc(r[root])<=Q[u].calc(r[root]);
        if(lsign==rsign)
        {
            if(!lsign)id[root]=ins;
        }
        else
        {
            double PL=Cross(Q[ins],Q[u]);
            if(abs(PL-((int)PL))<eps&&lsign==0)PL-=1;
            const int pl=PL;
            if(pl<=mid[root])
            {
                if(lsign)id[root]=ins,add(root<<1,u);
                else add(root<<1,ins);
            }
            else
            {
                if(rsign)id[root]=ins,add(root<<1|1,u);
                else add(root<<1|1,ins);
            }
        }
    }
}
void insert(const int root,const int ll,const int rr,const int ins)
{
    if(l[root]==ll&&r[root]==rr)
    {
        add(root,ins);
        return;
    }
    if(rr<=mid[root])insert(root<<1,ll,rr,ins);
    else if(ll>mid[root])insert(root<<1|1,ll,rr,ins);
    else insert(root<<1,ll,mid[root],ins),insert(root<<1|1,mid[root]+1,rr,ins);
}
int search(const int root,const int wan)
{
    if(l[root]==r[root])return id[root];
    int res=0;
    if(wan<=mid[root])res=search(root<<1,wan);
    else res=search(root<<1|1,wan);
    const double v1=Q[id[root]].calc(wan),v2=Q[res].calc(wan);
    if(abs(v1-v2)<eps)return min(res,id[root]);
    if(v1>v2)return id[root];
    return res;
}
int main()
{
    read(n);
    ini(1,1,39989);
    while(n--)
    {
        int opt;read(opt);
        if(opt==0)
        {
            int x;read(x),x=(x+lastans-1)%39989+1;
            lastans=search(1,x),print(lastans),putchar('\n');
        }
        else
        {
            int X0,Y0,X1,Y1;
            read(X0),X0=(X0+lastans-1)%39989+1;
            read(Y0),Y0=(Y0+lastans-1)%1000000000+1;
            read(X1),X1=(X1+lastans-1)%39989+1;
            read(Y1),Y1=(Y1+lastans-1)%1000000000+1;
            if(X0>X1||(X0==X1&&Y0>Y1))swap(X0,X1),swap(Y0,Y1);
            Q[++tot]=(segment){X0,Y0,X1,Y1};
            insert(1,Q[tot].x0,Q[tot].x1,tot);
        }
    }
    return flush(),0;
}

  • 然后其实还有个更清真的写法,不用线段求交,直接求在 m i d mid mid位置上的大小关系
    这个写法在我的这种写法下并不快,就只贴代码网址(这个是洛谷的提交记录,你需要AC后才能查看代码)了
  • 当然有更清真的算法,就是不要存点,直接存直线的一半表达式 y = k x + b y=kx+b y=kx+b里的 k , b k,b kb两个值,这样鲁棒性大大下降,但是速度有显著提升,同样只贴出代码网址

5 总结

这就是李超线段树了,似乎并不难,但是一般考试给出的题都不会是裸题,这个算法学习一下可以当 i d e a idea idea ^ _ ^

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值