题目链接:http://172.18.70.217/problem/16
题目大意:有 N 块磁石。一个人手中的石头可以通过磁力吸引地上的其它石头,而地上的石头不会互相吸引。人自己的坐标设为(x0,y0)(x0,y0)。地上第 i 块石头的坐标为 (xi,yi)(xi,yi),质量为 mi,磁力为 pi,吸引半 径为 ri。
人站在 (x0,y0)(x0,y0) 原地不动,不断地从已经获得的石头中拿起一块, 去吸引其它石头。若一块石头的“质量,与人的距离”分别不大于“人正在拿着的石头的磁力、吸引半径”,则该石头会被吸引到 (x0,y0)(x0,y0) 处。问最后能获得多少块石头?
解题思路:很容易想到的一个思路就是首先按照距离起点的距离进行排序。用一个队列存储我们得到的石头。我们可以在O(logn)的时间内,找到所有距离小于手上石头,但是我们发现,我们还要考虑质量和磁力之间的关系,但是质量并没有排序。我们自然而然的就想到了将整个序列分为若干小段,序列整体按照距离排序,方便距离的查找,但是小区间内部按照质量排序,方便找到对应距离后的质量的查找。这就是分块的思想,通常我们分为大体效果如图所示:
执行过程:
1.按照长度为√n分割出子区间,维护一个指针保存最靠前的没有被吸附的石头。
2.取出队首元素表示手上的石头,从小到大遍历区间。
3.如果区间尾部的元素的距离小于取出石头的吸附范围,从区间的指针开始遍历并入队同时移动指针,直到出现质量>磁力的情况出现为止。
4.如果区间尾部的元素的距离大于取出石头的吸附范围,从这个区间指针开始遍历到右端,将所有能被吸附的石头入队。
5.重复上述过程知道队列为空。
附上AC代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
typedef long long ll;
const ll maxn=250010;
struct Stone
{
ll d,r,m,p;
}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;
}