[JZOJ5390]维护直线

题目大意

这里写图片描述

分析

这道题可以是维护凸壳,直线加入的斜率还是单调的,可以直接用单调栈维护,然后二分查找。
但是我比较蠢,打了线段树维护,一开始还没打出来….
来复习一下怎么线段树维护凸壳值吧。
问题:我们插入若干条直线,询问某个x的最大函数值。
方法:对于线段树一个点x,假设他代表区间为[l,r]下标代表自变量的值,中点为mid,他的两个儿子代表的区间是[l,mid-1]和[mid+1,r]。我们在这个节点只储存一条直线,满足他在mid是的函数值最大。
插入直线l1的时候,假如这个点没有直线,那么把这条直线存在这里,退出。如果有直线l2,我们要分情况讨论。

  1. yl1(mid)<yl2(mid) ,那么如果 kl1<kl2 ,把l1递归进[l,mid-1],否则进入[mid+1,r]。为什么这么做呢?因为,l1在递归进去的区间里,l1有可能在一部分地方比l2大;而另外一个区间是不可能的。即使l1在任何地方都比l2差,传进去也没问题,因为我们已经保存了l2,查询的时候会碰到他,而l1有可能比l2好,我们不能放过这种可能性。
  2. yl1(mid)>yl2(mid) ,把上面的两条直线反过来即可,并且让x这个点储存l1。

查询自变量x的时候,我们一直递归进区间(x,x)(不存在就可能是因为他是某个mid),路上碰到的每条直线都更新一下答案。整体来看,这样做是对的。当然这个方法有些信息维护不了,更强大的方法要用平衡树。

代码

因为卡空间,我把自变量映射了一下。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
using namespace std;
#define cmax(a,b) (a=(a>b)?a:b)
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=4e5+5,mo=1e9+7;
struct rec
{
    int s,x,y,id;
}b[N];
bool operator <(rec a,rec b)
{
    return a.x<b.x||a.x==b.x&&a.s<b.s;
}
int d[N],v[N],vt,tr[N*2];
ll prt[N],ans,mx=4e18;
int n,m,i,j,k;
void clear()
{
    fo(i,1,n*4) tr[i]=0;
}
void change(int x,int l,int r,int y)
{
    if (l>r) return ;
    if (!tr[x])
    {
        tr[x]=y;
        return ;
    }
    int mid=(l+r)/2;
    ll vx=1ll*v[mid]*b[tr[x]].x+b[tr[x]].y,vy=1ll*v[mid]*b[y].x+b[y].y;
    if (vx>vy)// down vy
    {
        if (b[tr[x]].x>b[y].x)
            change(x*2,l,mid-1,y);else
            change(x*2+1,mid+1,r,y);
    }else
    {
        if (b[tr[x]].x<=b[y].x) 
            change(x*2,l,mid-1,tr[x]);else
            change(x*2+1,mid+1,r,tr[x]);
        tr[x]=y;
    }
}
void get(int x,int l,int r,int y)
{
    if (l>r||!tr[x]) return ;
    cmax(ans,1ll*b[tr[x]].x*b[y].y+b[tr[x]].y);
    int mid=(l+r)/2;
    if (b[y].y<=v[mid]) 
        get(x*2,l,mid-1,y);else
        get(x*2+1,mid+1,r,y);
}
int main()
{
    freopen("t2.in","r",stdin);
    freopen("t2.out","w",stdout);
    scanf("%d %d\n",&n,&m);
    fo(i,1,n) 
    {
        scanf("%d %d",&b[i].x,&b[i].y);
        b[i].s=0;
    }
    fo(i,1,m) 
    {
        scanf("%d %d",&b[i+n].x,&b[i+n].y);
        d[i]=b[i+n].y;
        b[i+n].s=1;
        b[i+n].id=i;
    }
    sort(b+1,b+1+n+m);
    sort(d+1,d+1+m);
    fo(i,1,m) if (d[i]!=d[i-1]) v[++vt]=d[i];
    fo(i,1,n+m)
    {
        if (!b[i].s) change(1,1,vt,i);
        else 
        {
            ans=-mx;
            get(1,1,vt,i);
            cmax(prt[b[i].id],ans-1ll*b[i].x*b[i].y);
        }
    }
    clear();
    fd(i,n+m,1)
    {
        if (!b[i].s)
        {
            b[i].x=-b[i].x;
            change(1,1,vt,i);
        }
        else
        {
            ans=-mx;
            get(1,1,vt,i);
            cmax(prt[b[i].id],ans+1ll*b[i].x*b[i].y);
        }
    }
    fo(i,1,m) printf("%lld\n",prt[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值