题目大意
有 N 块磁石。一个人手中的石头可以通过磁力吸引地上的其它石头,而地上的石头不会互相吸引。人自己的坐标设为(x0,y0)(x0,y0)。地上第 i 块石头的坐标为 (xi,yi)(xi,yi),质量为 mi,磁力为 pi,吸引半 径为 ri。
人站在 (x0,y0)(x0,y0) 原地不动,不断地从已经获得的石头中拿起一块, 去吸引其它石头。若一块石头的“质量,与人的距离”分别不大于“人正在拿着的石头的磁力、吸引半径”,则该石头会被吸引到 (x0,y0)(x0,y0) 处。问最后能获得多少块石头?
分析
整体思路:
对于可以吸引的磁石需要有两个条件,第一,距离必须在半径范围之内,第二,所吸引的磁石的质量必须小于手中磁石的磁力。只有这样才可吸引磁石。我们首先可以想到的对于距离我们进行排序,然后遍历距离范围之内的磁石,如果质量小于磁力那么就ans++,这种想法没有错误,但是对于数据范围这么大,就没法AC了。为了解决这一问题我们引入 分块这一思想——把一个较大的区间分成若干个小的区间,对于大的区间我们按照距离进行排序,对于小的区间我们按照质量进行排序,并用一个数组Maxdist【】来记录这一个小区间的最大值。
详细思路看代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
typedef long long ll;
const ll maxn=250010;//分块大小sqrt(maxn)
struct Stone
{
ll d,r,m,p; //两点之间的距离平方d,防止出现小数错误。磁力范围 r ,注意存入的时候平方
}sto[maxn];
ll Maxdist[maxn],sx,sy; //每一个小区间的最大质量,起止位置
ll lef[maxn],rig[maxn],n,x,y; //每一个小区间的左端点与右端点,用数组记录
bool vis[maxn]; //标记是否已经拿到了磁石
bool cmp_d(Stone a,Stone b)
{
return a.d<b.d; //按照距离排序
}
bool cmp_m(Stone a,Stone b)
{ //按照质量排序
return a.m<b.m;
}
int main()
{
scanf("%lld%lld%lld%lld%lld",&sx,&sy,&sto[0].p,&sto[0].r,&n);
sto[0].r*=sto[0].r;
for(ll i=1;i<=n;i++) //数据输入,注意平方
{
scanf("%lld%lld%lld%lld%lld",&x,&y,&sto[i].m,&sto[i].p,&sto[i].r);
sto[i].r*=sto[i].r;
sto[i].d=(sx-x)*(sx-x)+(sy-y)*(sy-y);
}
sort(sto+1,sto+n+1,cmp_d);
ll tot=0;
ll w=sqrt(n); //每一个小区间的大小
for(ll i=1;i<=n;i+=w)
{
lef[++tot]=i; //左端点
rig[tot]=min(n,i+w-1); //右端点
Maxdist[tot]=sto[rig[tot]].d; //质量最大值
sort(sto+lef[tot],sto+rig[tot]+1,cmp_m); //小区间排序
}
queue<ll> q; //把每次拿到的磁石放入这个队列中,用循环一次次取出,知道队列为空
q.push(0); //写入第一个磁石
ll ans=-1;
while(!q.empty())
{
ll u=q.front();
ans++;
q.pop();
ll rad=sto[u].r;
ll p=sto[u].p;
for(ll i=1;i<=tot;++i)
{
if(Maxdist[i]>rad)//如果磁石磁力小于小区间最大质量,那么遍历这个小区间
{
for(ll j=lef[i];j<=rig[i];j++)
{
if(!vis[j] && sto[j].d<=rad && sto[j].m<=p)
{
q.push(j);
vis[j]=true;
}
}
break;//不要忘记
}
while(lef[i]<=rig[i] && sto[lef[i]].m<=p)
{
if(!vis[lef[i]])
q.push(lef[i]);
++lef[i];
}
}
}
printf("%lld",ans);
return 0;
}