链接
https://ac.nowcoder.com/acm/problem/51114
题意
有 N N N 块磁石,每个磁石的性质可以用一个五元组 ( x , y , m , p , r ) (x,y,m,p,r) (x,y,m,p,r) 描述,其中 x , y x,y x,y 表示其坐标, m m m是磁石的质量, p p p 是磁力, r r r 是吸引半径
若磁石 A 与磁石 B 的距离不大于磁石A的吸引半径,并且磁石 B 的质量不大于磁石 A 的磁力,那么 A 可以吸引 B
现在有一块磁石 L,坐标为
(
x
0
,
y
0
)
(x0,y0)
(x0,y0),保持原地不动,所有可以被 L 吸引的磁石将会被吸引过来。
在每个时刻,可以选择更换任意一块自己已经获得的磁石(当然也可以是自己最初携带的L磁石)在
(
x
0
,
y
0
)
(x0,y0)
(x0,y0) 处吸引更多的磁石
最多能获得多少块磁石?
思路
分块
利用 BFS,建立队列,先将磁石 L 放入队列
将 n n n 块磁石按照质量从小到大排序并分成 n \sqrt{n} n 段,再将每一段中磁石按离 ( x 0 , y 0 ) (x0,y0) (x0,y0) 的距离从小到大排序
每次队头磁石吸引磁石时,必能找到第 k k k 段使:
1 ∼ k − 1 1 \sim k-1 1∼k−1 段中的每一块磁石的质量都小于等于队头的磁力
k + 1 k+1 k+1 段后的磁石的质量都大于队头的磁力
因此对于 1 ∼ k − 1 1 \sim k-1 1∼k−1 段的磁石,如果该块磁石未被吸引并且距离小于等于队头的磁力,入队,当扫描到第一个距离大于队头的磁力时,将该段的段首改为该点,这样已经被吸引的磁石就不会被扫描到
对于第 k k k 段的磁石,朴素扫描即可
时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int n,t;
ll x_0,y_0;
int cnt;
int L[500],R[500],M[500];
bool st[N];
int ans;
struct node {
int m,p;
ll d,r;
}s[N];
bool cmp1(node a,node b) {
return a.m<b.m;
}
bool cmp2(node a,node b) {
return a.d<b.d;
}
void pre() {
cin>>x_0>>y_0>>s[0].p>>s[0].r>>n;
s[0].r*=s[0].r;
for(int i=1;i<=n;i++) {
ll x,y,r;
int m,p;
cin>>x>>y>>m>>p>>r;
s[++cnt]={m,p,(x-x_0)*(x-x_0)+(y-y_0)*(y-y_0),r*r};
}
sort(s+1,s+1+n,cmp1);
t=sqrt(n);
for(int i=1;i<=t;i++) {
L[i]=R[i-1]+1;
R[i]=i*t;
}
if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;
for(int i=1;i<=t;i++) {
M[i]=s[R[i]].m;
sort(s+L[i],s+1+R[i],cmp2);
}
}
void bfs() {
queue<int>q;
q.push(0);
while(!q.empty()) {
int now=q.front();
q.pop();
for(int i=1;i<=t;i++) {
if(M[i]>s[now].p) {
for(int j=L[i];j<=R[i];j++) {
if(!st[j]&&s[j].m<=s[now].p&&s[j].d<=s[now].r) {
st[j]=true;
ans++;
q.push(j);
}
else if(s[j].d>s[now].r) break;
}
break;
}
while(L[i]<=R[i]&&s[L[i]].d<=s[now].r) {
if(!st[L[i]]) {
st[L[i]]=true;
ans++;
q.push(L[i]);
}
L[i]++;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
pre();
bfs();
cout<<ans<<endl;
return 0;
}