题解 P4254 【[JSOI2008]Blue Mary开公司】

· · 题解

题解&&李超线段树学习笔记

一.李超线段树解决的问题

考虑下面一个问题:

定义一个坐标系,有m次操作

操作1:添加一条直线

操作2:求x=x0这条直线和其他直线的交点的最高纵坐标

要求时间复杂度log级别,可以用线段树维护

二.原理

对于一个区间,我们维护该区间的所有直线中,没有被其他直线覆盖的从上往下去看在x轴上的投影最大的直线(称为标记直线)。

其实这条直线的意义就是对这个区间内大多数点而言最优的直线,但也不一定是最优的直线,更优解可能在更小的区间内。

现在插入一条直线(称为新直线)到了一个区间,尝试更改该区间的标记直线。

分为几种情况:

1.新直线完全覆盖标记直线,直接更新并return,不用继续向下更新。

2.新直线完全被覆盖,那么新直线无用,直接return。

3.不是上面两种情况,判断两条直线哪一个投影大一些,作为新的标记直线,再递归下去处理。

询问时直接整个线段树包含x0的区间所存的线段取max即可。

其实相当于一个标记永久化的思想。

三.细节

如何判断两条直线哪一个应该作为标记直线呢?

分类讨论,设原标记直线为y,新直线为x。

1.k[x]>k[y]

如图,蓝色的代表线段中垂线。

可以直观感受到如果斜率更大且在中点所对应的x上y更大,则投影更大。

还可以清楚认识到y直线对右边的区间已经没有贡献了,在右区间内x肯定优于y,但是y对左边可能还有贡献,所以把y放到左区间去更新一波答案就好。

2.k[x]<k[y]

道理同上,结论是如果斜率更小且在中点所对应的x上y更大,则投影更大。

较弱的那条直线对左区间没有贡献。

四.练习题

P4254 裸题,唯一的区别是纵截距给的是x==1的。

bzoj4515 听说是树剖+李超线段树

Code:

#include<bits/stdc++.h>
#define ll long long
#define db double
#define in inline
#define rint register int
#define ls (id<<1)
#define rs (id<<1|1)
using namespace std;
int n,t[200010],cnt;
db k[100010],b[100010];
char s[10];
in ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return x*f;
}
in db w (int id,int x) { return k[id]*(x-1)+b[id]; }
in void updata(int id,int l,int r,int x)
{
    if(w(x,l)>w(t[id],l)&&w(x,r)>w(t[id],r)) { t[id]=x; return; }//如果完全覆盖 直接return
    if(w(x,l)<=w(t[id],l)&&w(x,r)<=w(t[id],r)) return; //如果完全被覆盖 直接return
    //上面两种情况已经可以解决l==r时候的问题了 所以不用判断 
    int mid=(l+r)>>1;
    if(k[t[id]] < k[x])
    {
        //如果新加入直线的斜率更大,且中点纵坐标更大,更改标记直线
        if(w(x,mid) > w(t[id],mid)) updata(ls,l,mid,t[id]),t[id]=x; 
        //因为标记直线有可能在左区间还有贡献 所以把以前的标记直线丢到左区间 
        else updata(rs,mid+1,r,x);
        //如果纵坐标更小 那么新加入直线可能在右区间有贡献 
    }
    else //同理 
    {
        if(w(x,mid) > w(t[id],mid)) updata(rs,mid+1,r,t[id]),t[id]=x;
        else updata(ls,l,mid,x);
    }
}
in db query(int id,int l,int r,int x)
{
    if(l==r) return w(t[id],x);
    int mid=(l+r)>>1;
    if(x<=mid) return max(w(t[id],x),query(ls,l,mid,x));
    else return max(w(t[id],x),query(rs,mid+1,r,x));
}
int main()
{
    n=read();
    while(n--)
    {
        scanf("%s",s);
        if(s[0]=='P')
        {
            cnt++;
            scanf("%lf%lf",&b[cnt],&k[cnt]);
            updata(1,1,50005,cnt);
        }
        else
        {
            int x=read();
            printf("%d\n",(int)(query(1,1,50005,x)/100)) ;
        }
    }
    return 0;
}