CF1046I Say Hello

二分/三分,模拟

题意

平面上有两个人。有 \(n\) 个时刻,对于每个人,已知他在每个时刻的位置,且他们总会在两个位置间匀速移动。

如果他们的距离小于等于 \(d _ 1\),并且这是他们第一次交谈或者在他们上次互相打招呼后的某个时间点后,他们的距离大于 \(d_2\),那么他们会互相打一次招呼。

计算这两位朋友打招呼的次数。

\(2 \le n \le 10 ^ 5\);

\(0 < d _ 1 < d _ 2 < 1000\)

\(0 \le A _ x, A _ y, B _ x, B _ y \le 1000\)

题解

依据题意模拟即可。需要求出两个线段运动过程中的距离最小值,可以用三分(这里用二分实现)。

显然分为三种情况:距离递减、距离递增、距离先变小后变大。

可以二分峰值。考虑时刻 \(t\in [0,1]\),若 \(t\) 加上一个极小值后比当前更大,那么峰值就在左侧。反之同理。

代码

#include<bits/stdc++.h>
// #define int long long
using namespace std;
const int Maxn=1e5+10;
const double eps=1e-6;
int n,d1,d2,ans,flag;
struct pos
{
    double x,y;
    pos(double x=0,double y=0):x(x),y(y) {}
}a[Maxn],b[Maxn];
double dis(pos x,pos y)
{
    return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
}
pos calc(pos x,pos y,double p)
{
    return pos(x.x+(y.x-x.x)*p,x.y+(y.y-x.y)*p);  
}
double find(pos p1,pos p2,pos q1,pos q2)
{
    double re=dis(p2,q2),l,r,mid;
    l=eps,r=1;
    while(l<r+eps)
    {
        mid=(l+r)/2;
        if(dis(calc(p1,p2,mid),calc(q1,q2,mid))<dis(calc(p1,p2,mid+eps),calc(q1,q2,mid+eps))) re=min(re,dis(calc(p1,p2,mid),calc(q1,q2,mid))),r=mid-eps;
        else l=mid+eps;
    }
    return re;
}
pos operator+(pos x,int p) {return pos(x.x+p,x.y+p);}
signed main()
{
    cin>>n>>d1>>d2;
    for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y>>b[i].x>>b[i].y;
    ans=flag=bool(dis(a[1],b[1])<d1+eps);
    for(int i=2;i<=n;i++)
    {
        double mx,mn;
        mx=max(dis(a[i-1],b[i-1]),dis(a[i],b[i]));
        mn=find(a[i-1],a[i],b[i-1],b[i]);
        // cout<<i<<" "<<mx<<" "<<mn<<endl;
        if(fabs(mx-dis(a[i-1],b[i-1]))<eps)
        {
            if(mx>d2) flag=0;
            if(!flag && mn<d1+eps && mn<dis(a[i-1],b[i-1])) ans++,flag=(dis(a[i],b[i])+eps<d2);
        }
        else
        {
            if(dis(a[i-1],b[i-1])>d2) flag=0;
            if(!flag && mn<d1+eps) ans++,flag=1;
            if(mx>d2) flag=0;
        }
    }
    cout<<ans<<endl;
    return 0;
}
posted @ 2025-12-09 19:54  crazy--boy  阅读(4)  评论(0)    收藏  举报