AcWing 121. 赶牛入圈(二分、离散化、二维前缀和)

题干:

农夫约翰希望为他的奶牛们建立一个畜栏。

这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。

畜栏的边缘必须与X,Y轴平行。

约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。

多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。

只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。

请你帮约翰计算一下,能包含至少C单位面积三叶草的情况下,畜栏的最小边长是多少。
1≤C≤500
C≤N≤500

思路:

首先考虑存储问题,坐标范围[1,10000],用二维数组容易炸,而且不方便遍历,而点的个数只有500,所以我们可以把数据先离散化一下。

然后为了快速求出范围内三叶草的单位数,运用前缀和的知识,因为数据离散化过,所以map[i][j]+=map[i-1][j]+map[i][j-1]-map[i-1][j-1](map[i][j]代表以(i,j)做为右下角的矩形面积);
原理如图:
在这里插入图片描述
然后求长度,因为求得是满足条件的最小长度,所以我们对结果二分。
如果当前长度满足条件我们则二分左区间(找最小值嘛),否则二分右区间。

最后考虑二分的判断条件,我们需要确定两个点,一个左上角的点,一个右下角的点。然后因为数据离散化过,所以我们要保证两点之间的长度不能大于当前二分的长度。最后根据求前缀和的思路求出区域内三叶草的值。

注意的点:
①三叶草是1*1的小正方形,我们默认当前的点为正方形的右下角,那么我们最后在算长度的时候注意+1(即-(-1))。
②因为离散化需要一个找位置的过程,所以我们可以直接将x,y都放进一个容器中去排序后去重,然后(x,y)放进一个容器中方便找到离散化的值。
③注意三叶草可以重叠

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,c;
int map[1010][1010];
vector<pair<int,int> > poi;
vector<int> num;
int find(int t)
{
    return lower_bound(num.begin(),num.end(),t)-num.begin();
}
bool check(int len){
    int x1=1,x2=1,y1=1,y2=1;
    for(;x2<num.size();x2++){
        while(num[x2]-num[x1]+1>len)   x1++;
        for(y1=1,y2=1;y2<num.size();y2++){
            while(num[y2]-num[y1]+1>len)  y1++;
            //printf("%d %d %d %d\n",x2,y2,x1,y1);
            //printf("%d\n",map[x2][y2]-map[x1-1][y2]-map[x2][y1-1]+map[x1-1][y1-1]);
            if(map[x2][y2]-map[x1-1][y2]-map[x2][y1-1]+map[x1-1][y1-1]>=c)
                return true;
        }
    }
    return false;
}
int main()
{
    int x,y;
    scanf("%d%d",&c,&n);
    num.push_back(0);
    for(int i=0;i<n;i++){
        scanf("%d%d",&x,&y);
        poi.push_back({x,y});
        num.push_back(x);
        num.push_back(y);
    }
    sort(num.begin(),num.end());
    num.erase(unique(num.begin(),num.end()),num.end());
//    for(int i=0;i<num.size();i++){
//        printf("%d ",num[i]);
//    }
//    cout<<endl;
    for(int i=0;i<n;i++){
        x=find(poi[i].first);
        y=find(poi[i].second);
        map[x][y]++;
    }
    for(int i=1;i<num.size();i++){
        for(int j=1;j<num.size();j++){
            map[i][j]+=map[i-1][j]+map[i][j-1]-map[i-1][j-1];
            //printf("%d ",map[i][j]);
        }
        //cout<<endl;
    }
    int l=1,r=10100;
    while(l<r){
        int mid=(r+l)>>1;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
        //printf("%d %d\n",l,r);
    }
    printf("%d\n",r);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值