[CF70D]Professor‘s task

Professor’s task

题解

这道题很明显是让你动态维护凸包并判断点是否在凸包内。
这里给出一种很奇怪的动态维护凸包的方法,是笔者在上课时偶然想到的。
首先我们考虑凸包上的点具有什么特殊性质。

image-20210806222231759

很明显,凸包上的点一定会存在一条为某个斜率的直线从无限远处向凸包平移时第一个碰到它,也就是这个点与这个凸包"相切"。
我们设对于凸包上的点 ( x , y ) (x,y) (x,y),它的对应直线斜率为 k k k,明显有 y = k x + b y=kx+b y=kx+b。由于它是被第一个碰到的,所以 b b b 要么是所有点中最大的,要么是所有点中最小的。
我们将直线的表达式改写成一般式 a x + b y = c ax+by=c ax+by=c,我们的目的是让 c c c 最小。
而当 k k k 一定时 a a a b b b 一定呈某种倍数关系,这个倍数是一定的,所以我们当 a a a 一定时 b b b 只与 k k k 有关,我们不妨设 a + b = 1 a+b=1 a+b=1,可得 a x 0 ± ( 1 − a ) y 0 = a ( x 0 ± y 0 ) ∓ y 0 = c ax_0\pm(1-a)y_0=a(x_0\pm y_0)\mp y_0=c ax0±(1a)y0=a(x0±y0)y0=c
同样在一般式中依旧有对于凸包上的点,存在某个斜率使得该点对应的 c c c 是所有点中最大或者最小的,即 ∃ x , ( x 0 ± y 0 ) x ∓ y 0 = min ⁡ ( ( x i ± y i ) x ∓ y i ) \exist x,(x_0\pm y_0)x\mp y_0=\min((x_i\pm y_i)x\mp y_i) x,(x0±y0)xy0=min((xi±yi)xyi)
对于每个点,我们可以将 c c c 的值与斜率的关系转化成一个一次函数: f i ( x ) = ( x i ± y i ) x ∓ y i f_i(x)=(x_i\pm y_i)x\mp y_i fi(x)=(xi±yi)xyi。如果这个函数能在 x ∈ [ 0 , 1 ] x\in[0,1] x[0,1] 中存在比其它的 f f f 函数值都大/小的时候,那么它就一定是凸包上的点。

这明显可以用李超线段树来进行维护, x x x 的取值区间为 [ 0 , 1 ] [0,1] [0,1],在这个区间中用动态开点的方式维护所有点的函数,如果该函数在某个位置可以更新原线段树,那么它就可以成为凸包上的点。
插入一个点明显是 log ⁡   n \log\,n logn 的,直接像维护一次函数一样暴力插入即可。
而查询时可能出现它在这个位置上比当前节点的函数大,但却没有某个祖先的函数大的情况,所以我们需要在查询的过程中同时下传它比所有祖先的函数值都大的取值范围,这很明显可以直接解出来,在向子孙下传时需要更新,查询的时间复杂度仍然是 log ⁡   n \log\,n logn

总时间复杂度 O ( n log ⁡   n ) O\left(n\log\,n\right) O(nlogn),但由于要维护四个函数,常数可能会有点大。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int mo=998244353;
const int jzm=2333;
const int lim=15;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-7;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int q;
struct line{
    double k,b;line(){k=b=0;}
    line(double K,double B){k=K;b=B;}
    double ask(const double x){return k*x+b;}
};
struct ming{line mx;int lson,rson;ming(){mx=line(-1e9,-1e9);lson=rson=0;}};
class LiCao_SegmentTree{
    private:
        int tot;ming tr[MAXN];
    public:
        int root;line nd;
        void insert(int &rt,double l,double r,line aw,int dep){
            if(l>r||!dep)return ;if(!rt)rt=++tot;const double mid=(l+r)/2.0;
            if(tr[rt].mx.ask(mid)<aw.ask(mid))swap(tr[rt].mx,aw);
            if(tr[rt].mx.ask(l)<aw.ask(l))insert(tr[rt].lson,l,mid,aw,dep-1);
            if(tr[rt].mx.ask(r)<aw.ask(r))insert(tr[rt].rson,mid,r,aw,dep-1);
        }
        bool query(int rt,double l,double r,line aw,double al,double ar){
            if(al>ar)return 0;const double mid=(l+r)/2.0;
            if(!rt||(al<=mid&&mid<=ar&&tr[rt].mx.ask(mid)<aw.ask(mid)))return 1;double bl=al,br=ar;
            if(tr[rt].mx.ask(l)<aw.ask(l)||tr[rt].mx.ask(mid)<aw.ask(mid)){
                ar=min(ar,mid);
                if(tr[rt].mx.ask(al)>aw.ask(al))al=max(al,(tr[rt].mx.b-aw.b)/(aw.k-tr[rt].mx.k))+eps;
                if(tr[rt].mx.ask(ar)>aw.ask(ar))ar=min(ar,(tr[rt].mx.b-aw.b)/(aw.k-tr[rt].mx.k))-eps;
                if(query(tr[rt].lson,l,mid,aw,al,ar))return 1;
            }
            if(tr[rt].mx.ask(r)<aw.ask(r)||tr[rt].mx.ask(mid)<aw.ask(mid)){
                al=bl;ar=br;al=max(al,mid);
                if(tr[rt].mx.ask(al)>aw.ask(al))al=max(al,(tr[rt].mx.b-aw.b)/(aw.k-tr[rt].mx.k))+eps;
                if(tr[rt].mx.ask(ar)>aw.ask(ar))ar=min(ar,(tr[rt].mx.b-aw.b)/(aw.k-tr[rt].mx.k))-eps;
                if(query(tr[rt].rson,mid,r,aw,al,ar))return 1;
            }
            return 0;
        }
}T[4];
signed main(){
    read(q);
    for(int i=1;i<=q;i++){
        int opt,x,y;read(opt);read(x);read(y);
        line tmp1=line(1.0*x+1.0*y,-1.0*y),tmp2=line(1.0*x-1.0*y,1.0*y);
        line tmp3=line(-1.0*x-1.0*y,1.0*y),tmp4=line(1.0*y-1.0*x,-1.0*y);
        if(opt==1)
            T[0].insert(T[0].root,0.0,1.0,tmp1,lim),
            T[1].insert(T[1].root,0.0,1.0,tmp2,lim),
            T[2].insert(T[2].root,0.0,1.0,tmp3,lim),
            T[3].insert(T[3].root,0.0,1.0,tmp4,lim);
        else{
            bool tp1=T[0].query(T[0].root,0.0,1.0,tmp1,0.0,1.0);if(tp1){puts("NO");continue;}
            bool tp2=T[1].query(T[1].root,0.0,1.0,tmp2,0.0,1.0);if(tp2){puts("NO");continue;}
            bool tp3=T[2].query(T[2].root,0.0,1.0,tmp3,0.0,1.0);if(tp3){puts("NO");continue;}
            bool tp4=T[3].query(T[3].root,0.0,1.0,tmp4,0.0,1.0);if(tp4){puts("NO");continue;}
            puts("YES");
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值