【五校联考2day1】补给站

Description

WYF为了保证他自己能够吃到足够多的牛排,来补充自己的脑力,所以他建了两个补给站,坐标分别为(ax,ay),(bx,by)。他有n个休息地点,第i个休息地点的坐标是(xi,yi)。每个补给站都有一个补给半径,当一个休息地点在以一个补给站为圆心,该补给站的补给半径为半径的圆中时(包括在圆周上),那个休息地点就会获得补给。现在有m个询问,每个询问会给出第一个补给站的补给半径r1和第二个补给站的补给半径r2,WYF想知道有多少个休息地点会得到补给。

Input

输入的第一行包含2个整数,n与m。
第二行包含4个整数,ax,ay,bx,by。
第3至n+2行包含2个整数x,y。
第n+3至n+m+2行包含两个整数r1,r2。

Output

输出的第1至m行包含1个整数,表示其所对应的询问的答案。

Sample Input

4 2
-1 0 2 0
0 0
1 1
2 2
0 2
3 1
1 1

Sample Output

3
1
【样例说明】
对于第一个询问,第1,2,4个休息点都能受到补给。
对于第二个询问,只有第1个休息点能受到补给。

其实这种类似于覆盖的问题还是挺好做的,因为数据并没有要求我们强制在线,所以我们可以考虑一下用离线处理。

如果把题目简化成只有一个补给站,那么处理起来就简单了,先把每个休息点离补给站的距离排个序,然后对于每一个询问进行二分查找就很容易做出来了。

但题目是有两个补给站,所以就有可能出现都被两个补给站覆盖的重复的休息点,所以只要领用刚刚的思想,且找出重复的点,把他减掉就行了。

这时候询问可以离线的特点就可以排上用场了。先假设将点按照与第 1 个补给站距离排序后存入的数组为 a,按照与第 2 个补给站距离排序后存入数组 b。将所有询问按照第 1 个补给站的半径从小到大排序。如果按顺序遍历这些询问的话,对于每个询问 我们可以动态得知a 数组中,有前 xi 个点被第 1 个补给站覆盖。显然 xi−1≤xi,即当前询问比上一个询问可能多覆盖了 xi−xi−1 个点。这时候如果给这些新覆盖的点打上标记,同时查询 b 数组中距离在指定范围内、且没有被标记的点的个数,不就避免了重复计数的情况吗?
对于有特征的区间查询,我们可以考虑用树状数组或线段树解决,最后用二分查找答案就行了,实现时要注意一下细节。个人建议用树状数组,因为这毕竟打得短吗。呵呵。。
时间复杂度大概是O(n logn+m logm) 注意要开longlong,因为求距离是会爆掉int
  
 以下在树状数组实现下的代码

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 200005
#define M 100005

using namespace std;

struct Re
{
    long long x,y,z;
}b[N],Q[M];

struct Samjia
{
    long long x,y;
}a[N];
long long n,m,ax,ay,bx,by;
long long now=0,t=1;
long long ans[M],c[N],d[N],e[N],tree[N];

bool cmp(Samjia a,Samjia b)
{
    return (a.x<b.x);
}

bool cmp2(Re a,Re b)
{
    return (a.x<b.x || (a.x==b.x && a.y<b.y));
}

int Lowbit(int x)
{
    return (x&(x^(x-1)));
}

int GetSum(int p)
{
    long long ans=0;
    while (p) 
    {
        ans+=tree[p];
        p-=Lowbit(p);
    }
    return ans;
}

void Change(int p)
{
    while (p<=n)
    {
        tree[p]++;
        p+=Lowbit(p);
    }
}

void Find(int l,int r,int i)
{
    while (l<r)
    {
        int mid=(l+r)/2+1;
        if (e[mid]<=Q[i].y) l=mid;
        else r=mid-1;
    }
    ans[Q[i].z]=now+d[l]-GetSum(d[l]);
}

int main()
{
    scanf("%lld%lld",&n,&m);
    scanf("%lld%lld%lld%lld",&ax,&ay,&bx,&by);
    fo(i,1,n)
    {
        long long xx,yy;
        scanf("%lld%lld",&xx,&yy);
        a[i].x=1LL*(xx-ax)*(xx-ax)+1LL*(yy-ay)*(yy-ay);
        a[i].y=1LL*(xx-bx)*(xx-bx)+1LL*(yy-by)*(yy-by);
    }
    sort(a+1,a+1+n,cmp);
    fo(i,1,n)
    {
        b[i].x=a[i].y;
        b[i].y=a[i].x;
        b[i].z=i;
    }
    sort(b+1,b+1+n,cmp2);
    long long wz=0;
    b[0].x=e[0]=-1;
    long long tot=0;
    fo(i,1,n)
    {
        c[b[i].z]=i;
        if (b[i].x>b[wz].x)
        {
            d[tot]=i-1;
            e[++tot]=b[i].x;
            wz=i;
        }   
    }
    d[tot]=n;
    fo(i,1,m)
    {
        scanf("%lld%lld",&Q[i].x,&Q[i].y);
        Q[i].x=Q[i].x*Q[i].x;
        Q[i].y=Q[i].y*Q[i].y;
        Q[i].z=i;
    }
    sort(Q+1,Q+1+m,cmp2);
    fo(i,1,m)
    {
        while (t<=n && a[t].x<=Q[i].x)
        {
            now++;
            Change(c[t]);
            t++;
        }
        Find(0,tot,i);
    }
    fo(i,1,m) printf("%lld\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值