5140: [Usaco2017 Dec]A Pie for a Pie

题目:两只牛,牛A和牛B,他们对水果派的品味不同,每个人都有n<=1e5个派,每个派都有两种得分a[i], b[i],其中a[i]是牛A对该派的评分,b[i]是牛B的评分, 0 <= a[i], b[i] <= 1e9。两只牛开始礼尚往来,从牛A开始,它选一个pie给牛B,设这个pie的评分是(a[i], b[i])。此时牛B为了不失礼节(不太小气,也不要太谄媚)要选出一个B
评分在[b[i], b[i]+d]范围内的pie(a[j], b[j]),给A。同理,A又要选出一个A评分在[a[j], a[j]+d]范围内的pie给B,如此往复。每个pie都只能传递一次。如果A收到了一个A评分为0的pie,或者B收到了一个B评分为0的pie,那么礼尚往来就愉快的结束了。否则,就不愉快结束(即一个人无法选出合法的pie给对方时)。下面让你输出,
对于A的每一个pie,如果第一轮A把这个pie交给B,礼尚往来最少可以几轮愉快结束?如果无法愉快结束,就输出-1

Solution:

我们考虑倒推,首先,A的pie中b[i]=0的部分肯定只要一步就能完成,B的pie同理,我们将他们加进队列,然后依次用这些队列中的点去更新,我们取出一个点,如果是A的pie,那么那些B的pie能到它呢?设A的pie为(a[i],b[i]),则B的pie(a[j],b[j])需要满足a[i]-D<=a[j]<=a[i],我们就可以将B按a数组排序,每次就可以二分查找了。
但是加入数据厉害让你所有都匹配上就是N^2级别的匹配了,怎么办?设dis为答案注意到dis先确定的小,也就是说不会变了,那我们就可以把改好的连接起来,加速,这个过程可以用并查集实现,

        for (int i=l,father;i<=r;i=father+1)
        {
            father=find(i);
            fa[i]=r;
            if (dis[i]>dis[u]+1)
                dis[i]=dis[u]+1,q[R++]=i;
        }
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M=400010;
int fa[M],dis[M],q[M],ans[M];
int N,D,L,R;


struct node{
    int A,B,id;
}a[M];
bool cmp1(const node &a, const node &b)
{return a.B<b.B;}
bool cmp2(const node &a, const node &b)
{return a.A<b.A;}
int find(int x)
{return fa[x]==x?x:fa[x]=find(fa[x]);}
int bin1(int l,int r,int x)
{
    if (a[r].A<x) return -1;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (a[mid].A<x) l=mid+1;
        else r=mid;
    }
    return r;
}
int bin2(int l,int r,int x)
{
    if (a[l].A>x) return -1;
    while (l<r)
    {
        int mid=(l+r+1)>>1;
        if (a[mid].A>x) r=mid-1;
        else l=mid;
    }
    return l;
}
int bin3(int l,int r,int x)
{
    if (a[r].B<x) return -1;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (a[mid].B<x) l=mid+1;
        else r=mid;
    }
    return r;
}
int bin4(int l,int r,int x)
{
    if (a[l].B>x) return -1;
    while (l<r)
    {
        int mid=(l+r+1)>>1;
        if (a[mid].B>x) r=mid-1;
        else l=mid;
    }
    return l;
}
int main()
{
    L=R=0;
    scanf("%d%d",&N,&D);
    for (int i=1;i<=2*N;i++)
    {
        scanf("%d%d",&a[i].A,&a[i].B);
        a[i].id=i; fa[i]=i;
    }
    sort(a+1,a+1+N,cmp1);
    sort(a+1+N,a+1+N+N,cmp2);
    memset(dis,0x3f,sizeof(dis));
    for (int i=1;i<=N && !a[i].B;i++)
        q[R++]=i,dis[i]=1;
    for (int i=N+1;i<=N+N && !a[i].A;i++)
        q[R++]=i,dis[i]=1;
    while (L<R)
    {
        int u=q[L++],l,r;
        if (u<=N)
        {
            l=bin1(N+1,N+N,a[u].A-D);
            r=bin2(N+1,N+N,a[u].A);
        }
        else
        {
            l=bin3(1,N,a[u].B-D);
            r=bin4(1,N,a[u].B);
        }
        if (l==-1 || r==-1) continue;
        for (int i=l,father;i<=r;i=father+1)
        {
            father=find(i);
            fa[i]=r;
            if (dis[i]>dis[u]+1)
                dis[i]=dis[u]+1,q[R++]=i;
        }
    }
    for (int i=1;i<=N;i++)
        ans[a[i].id]=dis[i];
    for (int i=1;i<=N;i++)
        if (ans[i]<0x3f3f3f3f) printf("%d\n",ans[i]);
        else puts("-1");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值