AcWing 250. 磁力块 (分块+贪心)

250. 磁力块

题目链接

题意:给你n个散落在二维平面的磁铁,有(m,p,r)三个属性。m表示重量,p表示磁力,r表示作用半径。A磁铁可以将B磁铁吸引过来,当且仅当 p A > = m B    A N D    r > = d i s ( A , B ) p_{A}>=m_{B} \ \ AND \ \ r>=dis(A,B) pA>=mB  AND  r>=dis(A,B) ,一开始我们在点 ( x 0 , y 0 ) (x_{0},y_{0}) (x0,y0)处有一块初始磁铁。问最终你可以吸引多少块磁铁到点 ( x 0 , y 0 ) (x_{0},y_{0}) (x0,y0)处。

数据范围:
1≤N≤250000
−10^9 ≤x,y≤ 10^9
1≤m,p,r≤10^9

如果我们可以将另一块m很小,但p,r却很大的磁铁吸引过来,就可以用这块再吸引其他块。所以我们考虑bfs,用bfs来更新当前磁铁可以吸引的其他磁铁,然后将其入队,再更新。这样最多操作n次,即将所有磁铁入队。

接下来我们考虑如何来找可以更新的点,如果暴力的话,最终复杂度是 O ( n 2 ) O(n^2) O(n2)的。

鉴于分块的思想,我们可以按重量排序,然后将其每段分成 n \sqrt n n 块,每一块内按与 ( x 0 , y 0 ) (x_{0},y_{0}) (x0,y0)的距离排序,因为最终我们是要找多少个点可以被吸引到 ( x 0 , y 0 ) (x_{0},y_{0}) (x0,y0)处。

比如我们队头元素是k,那么如果一块内的最大值都比k.p要小的话,那么我们二分找距离小于k.r的值然后向前遍历入队,但又因为已经有序且每次都要加入队列,所以我们每次将st[i]++即可。对于最大值比k.p还大的情况,因为这一块已经比k.p大了,那么其后面的块的重量肯定比k.p都大,没有必要找。对这一块我们只要暴力找满足条件的值即可。第一种情况找块最多有 n \sqrt n n 次,每次入队均摊是O(1)。第二种自然是在块里找满足条件的值,最大操作 n \sqrt n n 次。所以更新点的复杂度为O( n \sqrt n n )。

时间复杂度 O ( n n ) O(n\sqrt n) O(nn )

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<string>                        
#include<algorithm>
#include<fstream>
#include<cmath>
#include<map>
#include<set>
#include<stack>
using namespace std;
typedef long long ll;
const int maxn=3e5+10;        
struct node{
    int m,p;
    ll dis,r;
}a[maxn];
bool cmp(node x1,node y1)
{
    return x1.m<y1.m;
}
bool cmp1(node x1,node y1)
{
    return x1.dis<y1.dis;
}
int st[maxn],ed[maxn];
int vis[maxn],dp[maxn];         //dp[i]:第i块中重量最大是多少
int main()               
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll x,y;int p,r,n;cin>>x>>y>>p>>r>>n;
    for(int i=1;i<=n;i++)
    {
        ll x1,y1;cin>>x1>>y1>>a[i].m>>a[i].p>>a[i].r;
        a[i].dis=(x1-x)*(x1-x)+(y1-y)*(y1-y);
        a[i].r*=a[i].r;         //dis的平方
    }
    sort(a+1,a+1+n,cmp);                 //按重量排序
    int sq=sqrt(n);
    for(int i=1;i<=sq;i++)
    st[i]=sq*(i-1)+1,ed[i]=sq*i,dp[i]=a[ed[i]].m;
    ed[sq]=n;dp[sq]=a[n].m;
    for(int i=1;i<=sq;i++)
    sort(a+st[i],a+ed[i]+1,cmp1);           //块里按距离排序
    queue<node>q;
    int ans=0;
    q.push({0,p,0,1ll*r*r});
    while(!q.empty())
    {
        node k=q.front();q.pop();
        for(int i=1;i<=sq;i++)
        {
            if(dp[i]>k.p) {           //到这块截至 sqrt(n)
                for(int j=st[i];j<=ed[i];j++)
                {
                    if(k.r<a[j].dis) break;
                    else if(!vis[j]&&k.p>=a[j].m)
                    {
                        vis[j]=1;
                        q.push(a[j]);
                        ans++;
                    }
                }
                break;
            }
            while(st[i]<=ed[i])
            {
                if(a[st[i]].dis>k.r) break;
                if(!vis[st[i]]) {
                    ans++;q.push(a[st[i]]);
                }
                st[i]++;
            }
        }
    }
    cout<<ans<<endl;
    return 0;  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值