2017.9.28 线段树永久标记 解题报告

Description

给出m个事件。
1. 在坐标系中给出从(x1,y1)到(x2,y2)的线段
2. 询问当在x坐标为a时,与最低的线段的高度,给出此时y的坐标。

Input

第一行一个整数m,表示事件数。
接下来m行,每行有若干正整数,第一个数D表示事件类型。
若D=1,表示添加一条线段,四个整数x1,y1,x2,y2。
若D=2,接下来一个正整数a,询问在x=a处的答案。
对于100%的数据,1≤m≤200000,1≤x,y,a≤10^9,x1<x2。

Output

如题目所示

【解题报告】
这道题有点像BZOJ3165
我们的方法是用一颗线段树来解决。
线段树维护的是当前区间最优线段的左端点高度和右端点高度。
那么什么是最优线段
显然,对于一个区间,一定会有许多条完全覆盖这个区间的线段,对于区间内的一些点,它上面最低的直线可能是线段A,而对于区间内的另外一些点,它上面最低的直线可能是线段B,线段C。。。。我们维护这个区间时不可能将这些直线全部记录,所以我们钦定一条最优线段,也就是这条线段可以覆盖这一段区间尽可能多的点。
怎么更新呢?
如果我们插入一条线段,如果在当前区间中,它的左右端点都在区间的最优线段之上,那它一定是不优的。如果在当前区间中,它的左右端点都在区间的最优线段之下,那它直接成为当前区间的左右线段。
如果相交的话,就需要分情况讨论它是否能取代最优线段了。
怎么查询呢?
这个很好想,我们二分递归查找所有覆盖查询点的线段,不断更新答案就可以了。
(因为我们在查询的过程中会遍历所有可能成为最优线段的线段)

注意要动态开点

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

int m,root,tn;
const int maxn=200010,maxlog=30,inf=1000000000;
const double eps=1e-8;

struct Node
{
    int L,R;
    double lv,rv;
}tree[maxn*maxlog];

double get(int x,int X1,double V1,int X2,double V2)
{
    return (V2-V1)*(x-X1)/(X2-X1)+V1;
}
int modify(int cur,int l,int r,int x1,double v1,int x2,double v2)
{
    if(!cur)
    {
        cur=++tn;
        tree[cur].lv=tree[cur].rv=inf;
    }
    int mid=(l+r)>>1;
    if(x1<=l&&x2>=r)
    {
        double lv=get(l,x1,v1,x2,v2),rv=get(r,x1,v1,x2,v2);
        double sl=lv-tree[cur].lv,sr=rv-tree[cur].rv;
        if(sl<=eps&&sr<=eps){tree[cur].lv=lv;tree[cur].rv=rv;return cur;}
        if(sl>=-eps&&sr>=-eps) return cur;
        if(fabs(sl)<fabs(sr))
        {
            if(sr<=eps)
            {
                tree[cur].L=modify(tree[cur].L,l,mid,l,tree[cur].lv,r,tree[cur].rv);
                tree[cur].lv=lv;
                tree[cur].rv=rv;
            } 
            else tree[cur].L=modify(tree[cur].L,l,mid,x1,v1,x2,v2);
        } 
        else 
        {
            if(sl<=eps)
            {
                tree[cur].R=modify(tree[cur].R,mid+1,r,l,tree[cur].lv,r,tree[cur].rv);
                tree[cur].lv=lv;
                tree[cur].rv=rv;
            } 
            else tree[cur].R=modify(tree[cur].R, mid+1, r, x1, v1, x2, v2);
        }
    } 
    else 
    {
        if(x1<=mid) tree[cur].L=modify(tree[cur].L,l,mid,x1,v1,x2,v2);
        if(x2>mid) tree[cur].R=modify(tree[cur].R,mid+1,r,x1,v1,x2,v2);
    }
    return cur;
}
double query(int cur,int l,int r,int pos)
{
    if(!cur) return inf;
    if(l==r) return tree[cur].lv;
    int mid=(l+r)>>1;
    double ans=get(pos,l,tree[cur].lv,r,tree[cur].rv);
    if(pos<=mid) return min(ans,query(tree[cur].L,l,mid,pos));
    else return min(ans,query(tree[cur].R,mid+1,r,pos));
}
int main()
{
    freopen("defense.in","r",stdin);
    freopen("defense.out","w",stdout);
    scanf("%d",&m);
    int x1,x2;
    double v1,v2;
    while(m--)
    {
        int opt;scanf("%d",&opt);
        if(opt==1)
        {
            scanf("%d%lf%d%lf",&x1,&v1,&x2,&v2);
            root=modify(root,1,inf,x1,v1,x2,v2);
        } 
        if(opt==2)
        {
            scanf("%d",&x1);
            printf("%lf\n",query(root,1,inf,x1));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值