题解 P6214 【「SWTR-04」Taking a Walk】

· · 题解

应出题人要求,前来研究本题,然后发现出题人的写法一点也不美观。

我们考虑任意时刻两人之间的向量 \vec{s}。在某一段时间内,两人之间向量的变化率会一直是 \vec{v}。于是,按照此种方式进行 t 秒后,二人之间的向量会是 \vec{s}+t\vec{v}

考虑两人间的距离,它为 |\vec{s}+t\vec{v}|。我们如果画出图来,就会发现它的几何意义是原点到直线上一点的距离。

明显,点到直线距离是二次函数(考虑点到直线距离公式)。这就意味着,我们如果把每一时刻二人间距离画出来,它可能长这样:

如图,距离可能曲里拐弯,可能上下乱动,但是唯一不变的是,在每个区间内,距离要么是单调上升/下降的,要么是单峰的。

于是,我们可以再把每个单峰函数在峰值处切一刀,将其切成两半单调的函数。

于是现在,我们便得到了众多单调的连续函数。

我们现在再考虑询问距离为 x,则其就相当于用一条水平的直线去截上述图像,求出其某个交点。

明显,依据中值定理,对于一段函数 (t_i,t_{i+1}),如果其一端在该水平直线上,一端在该水平直线下,则二者必在此段区间内有且仅有一个交点。

于是我们便可以离线下所有询问,然后从小到大依次枚举每一段 (t_i,t_{i+1}),更新那些与其有交点的询问。

具体而言,我们可以将询问按照键值排序,关于询问建线段树,然后在线段树上维护区间 \min,表示此段区间里剩余出现次数最少的那个询问的出现次数。当我们有一段函数的时候,就把它执行区间 -1 的操作。假如何时发现出现了 0,就证明出现 0 的询问与此段函数的交点是该询问需要的交点,找出该交点即是此询问的答案。

说起来很轻巧,但是写起代码来却异常恶心。

首先,初始的函数可以通过对两个人进行归并得到。这时,我们便知道了每一段函数的分界点、函数在分界点处的取值,以及每段区间内部的速度(即上述的 v)。

然后,关于将二次函数切开的操作,可以使用三分——但我认为那是异端行为。更好的方法是采用计算几何求出原点到上述直线的垂线,然后在垂足处将二次函数分开。

然后建树什么的就不提了。注意,区间的端点要单独考虑,不然会出现奇怪的错误(因为端点会被左右两端的区间各计算一次)。

然后就是求询问与某段函数的交点了。可以使用二分——但那还是异端行为。更好的方法是直接上计算几何,计算出当直线上的某个点离原点的距离是询问的值时,其与垂足间的距离(可以直接上勾股定理算得),然后判断垂足两端距离它为上述距离的点中哪一个在区间内即可。

最后是两种特殊情况:-2.33,当且仅当函数中某一段是水平直线(这是有可能发生的,此时 \vec{v}=0),且该直线与询问直线重合;-4.66,当且仅当所有区间都加入线段树后,某些询问还有剩余的次数。

时间复杂度 O(q\log q)

(所以,为什么还要用主席树呢?)

(虽然此代码一点都不比主席树短)

代码:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-10;
struct Vector{
    double x,y;
    Vector(){x=y=0;}
    Vector(double X,double Y){x=X,y=Y;}
    friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
    friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
    friend Vector operator *(const Vector &u,const double &v){return Vector(u.x*v,u.y*v);}
    friend Vector operator /(const Vector &u,const double &v){return Vector(u.x/v,u.y/v);}
    friend double operator &(const Vector &u,const Vector &v){return u.x*v.y-u.y*v.x;}//cross times
    friend double operator |(const Vector &u,const Vector &v){return u.x*v.x+u.y*v.y;}//point times
    double operator ~()const{return sqrt(x*x+y*y);}//the modulo of a vector
    double operator !()const{return atan2(y,x);}//the angle of a vector
    void read(){scanf("%lf%lf",&x,&y);}
    void print(){printf("(%lf,%lf)",x,y);}
};
typedef Vector Point;
struct Line{
    Point x,y;
    Vector z;
    Line(){}
    Line(Point X,Point Y){x=X,y=Y,z=Y-X;}
    friend Point operator &(const Line &u,const Line &v){return u.x+u.z*((v.z&(u.x-v.x))/(u.z&v.z));}
};
typedef Line Segment;
namespace ini{//initialization part.
    int n,m,lim;
    Vector A[80100],B[80100],v[160100];
    double aa[80100],bb[80100],tim[160100];
    Point p[160100],tange[160100];
    double FindTangent(int i){
        if(~v[i+1]<eps||~p[i]<eps){tange[i]=p[i];return 0;}
        Line U=Line(p[i],p[i]+v[i+1]);
        Line V=Line(Point(),Point(v[i+1].y,-v[i+1].x));
        Point W=U&V;
        tange[i+1]=W;
        W=W-p[i];
        if((W|v[i+1])<0)return 0;//the inflection point is <0.
        return ~W/~v[i+1];
    }
}
namespace nin{//function part.
    int q,lim; 
    Vector v[320100];
    double tim[320100];
    Point p[320100],tange[320100];
    double FindConcide(int i,double req){
        double mini=~tange[i+1];
        double nedi=sqrt(req*req-mini*mini)/~v[i+1];
        Point W=tange[i+1]-p[i];
        double nowi=~W/~v[i+1];
        if((W|v[i+1])<0)nowi=-nowi;
        if(nowi+nedi>eps&&nowi+nedi<=tim[i+1]-tim[i])return nowi+nedi+tim[i];
        return nowi-nedi+tim[i];
    }
    struct Query{
        int num,id;
        double inq;
        friend bool operator <(const Query &x,const Query &y){return x.inq<y.inq;}
    }qq[301000];
    double qwq[301000];
    double res[301000];
    set<double>sp;
    bool findsp(double inq){
        auto it=sp.lower_bound(inq);
        if(it!=sp.end()&&fabs(*it-inq)<eps)return true;
        if(it==sp.begin())return false;
        it--;
        return fabs(*it-inq)<eps;
    }
    #define lson x<<1
    #define rson x<<1|1
    #define mid ((l+r)>>1)
    struct Segtree{
        int tag,mn;
    }seg[1201000];
    void ADD(int x,int y=1){seg[x].tag+=y,seg[x].mn-=y;}
    void pushdown(int x){ADD(lson,seg[x].tag),ADD(rson,seg[x].tag),seg[x].tag=0;}
    void pushup(int x){seg[x].mn=min(seg[lson].mn,seg[rson].mn);}
    void build(int x,int l,int r){
        if(l==r){
            if(findsp(qq[l].inq))res[qq[l].id]=-2.33,seg[x].mn=0x3f3f3f3f;
            else seg[x].mn=qq[l].num;
            return;
        }
        build(lson,l,mid),build(rson,mid+1,r),pushup(x);
    }
    void modify(int x,int l,int r,int L,int R){
        if(l>R||r<L)return;
        if(L<=l&&r<=R){ADD(x);return;}
        pushdown(x),modify(lson,l,mid,L,R),modify(rson,mid+1,r,L,R),pushup(x);
    }
    void getans(int x,int l,int r,int i){
        if(l==r){
            res[qq[l].id]=FindConcide(i,qq[l].inq);
            seg[x].mn=0x3f3f3f3f;
            return;
        }
        pushdown(x);
        if(seg[lson].mn==0)getans(lson,l,mid,i);
        if(seg[rson].mn==0)getans(rson,mid+1,r,i);
        pushup(x);
    }
    void setans(int x,int l,int r,double i){
        if(l==r){
            res[qq[l].id]=i;
            seg[x].mn=0x3f3f3f3f;
            return;
        }
        pushdown(x);
        if(seg[lson].mn==0)setans(lson,l,mid,i);
        if(seg[rson].mn==0)setans(rson,mid+1,r,i);
        pushup(x);
    }
}
namespace ini{
    void initialize(){
        scanf("%d%d%d",&n,&m,&nin::q);
        A[0].read(),B[0].read();
        for(int i=1;i<=n;i++)A[i].read(),scanf("%lf",&aa[i]);
        for(int i=n;i;i--)A[i]=(A[i]-A[i-1])/(aa[i]-aa[i-1]);
        for(int i=1;i<=m;i++)B[i].read(),scanf("%lf",&bb[i]);
        for(int i=m;i;i--)B[i]=(B[i]-B[i-1])/(bb[i]-bb[i-1]);
        for(int i=1,j=1;i<=n&&j<=m;){
            tim[++lim]=min(aa[i],bb[j]);
            v[lim]=A[i]-B[j];
            if(fabs(aa[i]-tim[lim])<eps)i++;
            if(fabs(bb[j]-tim[lim])<eps)j++;
        }
        p[0]=A[0]-B[0];
        for(int i=1;i<=lim;i++)p[i]=p[i-1]+v[i]*(tim[i]-tim[i-1]);
    }
}
namespace nin{
    void solution(){
//      p[0].print(),puts("");for(int i=1;i<=lim;i++)v[i].print(),printf("%lf\n",tim[i]),p[i].print(),puts("");
        for(int i=0;i<lim;i++)if(~v[i+1]<eps)sp.insert(~p[i]);
        for(int i=1;i<=q;i++)scanf("%lf%d",&qq[i].inq,&qq[i].num),qq[i].id=i;
        sort(qq+1,qq+q+1);
//      for(int i=1;i<=q;i++)printf("%lf %d %d\n",qq[i].inq,qq[i].num,qq[i].id);
        for(int i=1;i<=q;i++)qwq[i]=qq[i].inq;
        build(1,1,q);
        for(int i=0;i<lim;i++){
            if(~v[i+1]<eps)continue;
            double l=~p[i],r=~p[i+1];
            if(l>r)swap(l,r);
            l+=eps,r-=eps; 
//          printf("%lf %lf\n",l,r);
            int L=upper_bound(qwq+1,qwq+q+1,l)-qwq;
            int R=upper_bound(qwq+1,qwq+q+1,r)-qwq-1;
//          printf("%d %d\n",L,R);
            if(L<=R)modify(1,1,q,L,R);
//          printf("BEF:%d\n",seg[1].mn);
            if(seg[1].mn==0)getans(1,1,q,i);
//          printf("AFT:%d\n",seg[1].mn);
            double w=~p[i+1];
            L=lower_bound(qwq+1,qwq+q+1,w)-qwq;
            R=upper_bound(qwq+1,qwq+q+1,w)-qwq-1;
            if(L<=R)modify(1,1,q,L,R),printf("%d %d\n",L,R);
            if(seg[1].mn==0)setans(1,1,q,tim[i+1]);
        }
        for(int i=1;i<=q;i++)if(fabs(res[i])<eps)res[i]=-4.66;
        for(int i=1;i<=q;i++)printf("%.9lf\n",res[i]);
    }
}
int main(){
    ini::initialize();
    nin::p[0]=ini::p[0];
    for(int i=0;i<ini::lim;i++){
        double pos=ini::FindTangent(i);
        if(pos>eps&&(ini::tim[i+1]-ini::tim[i])-pos>eps){
            nin::lim++;
            nin::p[nin::lim]=ini::p[i]+ini::v[i+1]*pos;
            nin::v[nin::lim]=ini::v[i+1];
            nin::tim[nin::lim]=ini::tim[i]+pos;
            nin::tange[nin::lim]=ini::tange[i+1];
        }
        nin::lim++;
        nin::p[nin::lim]=ini::p[i+1];
        nin::v[nin::lim]=ini::v[i+1];
        nin::tim[nin::lim]=ini::tim[i+1];
        nin::tange[nin::lim]=ini::tange[i+1];
    }
    nin::solution();
    return 0;
}