Codeforces 198E 题解

题面

机翻题目描述

有一天,流浪者 Qwerty 目睹了两艘运输船相互碰撞。结果,他们所有货物的容纳物散落在整个空间中。现在,Qwerty 希望选择尽可能多的丢失物品​​,以便以后出售。

事实是,两艘船上都有很多新的重力式抓爪,已运往市场。抓取器是一种可以安装在宇宙飞船上的设备,然后将其从太空中吸取到并将其运输到船上的货舱。

总体而言,坠毁的船舶失去了 $n$ 个重力抓取器:第 $i$ 个抓取器位于坐标为 $(x_i,y_i)$ 的点上。每个抓取器都有两个特征- $p_i$(力量)和 $r_i$(动作半径),并且可以抓握质量不超过 $p_i$ 且距离不超过 $r_i$ 的任何物品。抓取器本身也是一个项目,其质量为mi。

Qwerty 的船位于 $(x,y)$ 点,并安装了旧的电磁夹具,其特征为 $p$ 和 $r$。船上的货舱中没有其他抓爪。

找到 Qwerty 可以容纳的最大数量的抓手。当他拾取物品时,他可以任意地在船上的货舱中安装任何抓爪,包括他刚刚抓起的抓爪。在任何时候,船舶只能安装一个抓爪。我们会考虑所有物品,当护林员拿起物品时,将考虑 Qwerty 的船舶是不动的,除了当抓爪移动物品时–然后物品移至货舱并且船舶仍然不动。我们可以假设船上的货舱有足够的空间容纳所有抓爪。Qwerty可以任意多次使用找到的任何夹具或初始夹具。

简化版题目描述

给你一个二维地图,船的坐标为 $(x,y)$,船上预置了一个磁铁,周边有 $n$ 个磁铁。对于每个磁铁,有自己的质量(船上预置的可视为0)、可吸引的最大质量,可吸引的距离。(不考虑衰减啥的)

问最后最多能吸引多少磁铁。

输入格式

第一行包含五个整数 $x,y,p,r,n$($-10^9 \leq x,y \leq 10^9, 1 \leq p, r \leq 10^9,1 \leq n \leq 250000$)—船的初始位置,初始抓手的特征和 碰撞过程中进入空间的抓爪数量。

接下来的 $n$ 行包含抓取器的描述:第 $i$ 行包含五个整数 $x_i,y_i,m_i,p_i,r_i$($-10^9 \leq x_i,y_i \leq 10^9, 1 \leq m_i,p_i,r_i \leq 10^9$)—第 $i$ 行抓手的坐标和特征。

确保所有抓爪都位于不同的位置。 与 Qwerty 的船不在同一地点。

输出格式

打印一个数字- Qwerty 可以吸引到他的飞船的最大抓手数量。您无需计算最初的旧磁铁夹具。

样例输入

0 0 5 10 5
5 4 7 11 5
-7 1 4 7 8
0 2 13 5 6
2 -3 9 3 4
13 5 1 9 9

样例输出

3

题解

思路

这个题很容易做一些简单的转化,例如所有 $(x,y)$ 只是用来求到船的距离 $dis$ ,因此可以将 $dis$ 求出。

之后的抓取磁铁,可以发现如果船上存在一个磁铁 $i$,那么它可以吸引任何满足 $ m \leq p_i, dis \leq r_i $ 的磁铁。

那么应该如何解决这个问题呢?如果只用满足单个条件,我们知道可以用树状数组等东西就可以求出了;但现在有了两个条件,因此需要用到神奇的树状数组+堆(算某种意义上的树套树吧)

在树状数组的每一个节点,我们存储此节点区间内的所有磁铁,并按照它们的质量从小到大排序。这样我们在判断一个已有的磁铁能吸取哪些其他磁铁时,就只需要在 query 过程中,将每个节点上的堆顶弹到大于 $p$,弹出的元素即是可以吸取的磁铁。

而为了避免重复弹出,可以再在磁铁上存储一个 $del$ 属性。

所以我们只用将所有磁铁插入到这样的结构中,然后将船上预置的磁铁先插入队列中,用队列来不断吸取新的磁铁,使用旧的磁铁,最后即可得到答案。

代码(1308ms, 47MB)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int x,y,p,r,n;
struct gripper{
    int dis,m,p,r;
    ll disori;
    bool del=false;
}gp[250020];
int lshcnt;
ll lsh[500050];
priority_queue<pair<int,int> > c[500050];
queue<int> availgp;
inline int lowbit(int x){return x&-x;}
inline void insert(int p,int x){
    for(;p<=lshcnt;p+=lowbit(p)){
        c[p].push(make_pair(-gp[x].m,x));
    }
}
inline int query(int r,int m){
    int res=0;
    for(;r;r-=lowbit(r)){
        while(c[r].size()&&c[r].top().first>=-m){
            int x = c[r].top().second;
            c[r].pop();
            if(!gp[x].del){
                gp[x].del=true;
                availgp.push(x);
                ++res;
            }
        }
    }
    return res;
}
int main(){
    scanf("%d%d%d%d%d",&x,&y,&p,&r,&n);
    lsh[1]=r;
    for(int i=1,xi,yi;i<=n;++i){
        scanf("%d%d%d%d%d",&xi,&yi,&gp[i].m,&gp[i].p,&gp[i].r);
        gp[i].disori=ceil(sqrt(pow((double)(xi-x),2) + 
                               pow((double)(yi-y),2)));
        lsh[i*2]=gp[i].disori;
        lsh[i*2+1]=gp[i].r;
    }
    sort(lsh+1,lsh+1+n*2+1);
    lshcnt=unique(lsh+1,lsh+1+2*n+1)-lsh-1;
    for(int i=1;i<=n;++i){
        gp[i].dis=lower_bound(lsh+1,lsh+1+lshcnt,gp[i].disori)-lsh;
        gp[i].r=lower_bound(lsh+1,lsh+1+lshcnt,gp[i].r)-lsh;
        insert(gp[i].dis,i);
    }
    r=lower_bound(lsh+1,lsh+1+lshcnt,r)-lsh;
    int ans=query(r,p);
    while(!availgp.empty()){
        int f=availgp.front();availgp.pop();
        ans+=query(gp[f].r,gp[f].p);
    }
    printf("%d\n",ans);
}

大佬代码(374ms 13.7MB)

CodeForces链接

#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define X first
#define Y second
#define fill(A,B) memset(A,B,sizeof(A))
#define For(i,A,B) for(int i=(A);i<(int)(B);i++)
#define rep(i,A,B) for(int i=(A);i<=(B);i++)
#define dep(i,B,A) for(int i=(B);i>=(A);i--)
#define re(i,A) for(int i=0;i<(int)(A);i++)
#define de(i,A) for(int i=(A)-1;~i;i--)
#define inf (1<<30)
#define mn 250011
#define P 1000000007
#define si(x) ((int)x.size())
#define MAX(A,B) A=max(A,B)
#define MIN(A,B) A=min(A,B)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
inline ll pow(ll a,ll b,ll mo){if(!a)return 0;ll tmp=1;for(;b;b>>=1){if(b&1)tmp=(ll)tmp*a%mo;a=(ll)a*a%mo;}return tmp;}
template<class T>inline void R(T &xx){xx=0;char ch=getchar();bool F=0;while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();if(ch=='-')F=1,ch=getchar();while(ch>='0'&&ch<='9')xx=xx+xx+(xx<<3)+ch-48,ch=getchar();if(F)xx=-xx;}

int n,x0,y0,p,l1,rr=1;pair<ll,int>q[mn];ll r;
struct nod{int p,m;ll d,r;inline bool operator<(const nod&b)const{return d<b.d;}}a[mn];

int seg[mn<<2]; 
#define ls (x<<1)
#define rs (x<<1|1)
void build(int x,int l,int r){
    if(l==r){
        seg[x]=a[l].m;return;
    }
    int mid=l+r>>1;
    build(ls,l,mid);build(rs,mid+1,r);
    seg[x]=min(seg[ls],seg[rs]);
}
void modi(int x,int l,int r,ll R,int p){
    if(seg[x]>p)return;
    if(l==r){
        q[rr++]=mp(a[l].r,a[l].p);
        seg[x]=inf;
        return;
    }
    int mid=l+r>>1;
    modi(ls,l,mid,R,p);modi(rs,mid+1,r,R,p);
    seg[x]=min(seg[ls],seg[rs]);
}
void que(int x,int l,int r,ll R,int p){
    if(a[l].d>R)return;
    if(a[r].d<=R){modi(x,l,r,R,p);return;}
    int mid=l+r>>1;
    que(ls,l,mid,R,p);que(rs,mid+1,r,R,p);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    //freopen("2.out","w",stdout);
#endif
    R(x0),R(y0);R(p),R(r),R(n);r*=r;
    int x,y;rep(i,1,n)R(x),R(y),R(a[i].m),R(a[i].p),R(a[i].r),a[i].r*=a[i].r,a[i].d=(ll)(x0-x)*(x0-x)+((ll)y0-y)*(y0-y);
    sort(a+1,a+1+n);
    build(1,1,n);
    for(q[0]=mp(r,p);l1<rr;){
    //  cout<<q[l1].X<<" "<<q[l1].Y<<endl;
        que(1,1,n,q[l1].X,q[l1].Y);l1++;
    }
    printf("%d\n",rr-1);
    return 0;
}

大致分析了一下,大佬以排序后磁铁的下标建树,用线段树维护区间最小质量,个人认为非常巧妙的地方,就是将 $dis$ 与 $r$ 进行比较来选取应修改的子树,之后将线段树维护的最小质量同 $p$ 比较,找到适当的磁铁并加入队列,之后直接将其质量赋为 $inf$ 来自然删除。

总结

线段树在使用时不用按照我传统的思维,去单纯通过下标来进行各种操作,而完全可以通过比较其他的单调信息来操作。

在重构时也应当对上一版本程序固化的一些思维进行清理,如这道题一开始我打算通过堆来错误地贪心选取磁铁,但后面想到正确解法时却无法舍弃堆这个思维,导致题虽然做对了但并不优,还去使用了一些自己不太熟悉的东西,不过收获就是略微明白了一些树套树的概念。