AcWing 121 赶牛入圈

题目描述:

农夫约翰希望为他的奶牛们建立一个畜栏。这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。畜栏的边缘必须与X,Y轴平行。

约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。

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

输入格式

第一行输入两个整数 C 和 N。接下来 N 行,每行输入两个整数 X 和 Y,代表三叶草所在的区域的X,Y坐标。同一行数据用空格隔开。

输出格式

输出一个整数,代表畜栏的最小边长。

数据范围

1≤C≤500,C≤N≤500

输入样例:

3 4
1 2
2 1
4 1
5 2

输出样例:

4

分析:

题目要求最小边长,我们可以从1开始枚举到10000,枚举复杂度为O(n),也可以二分边长的长度,复杂度为O(logn)。当枚举到长度为len的区域时,如何确定是否存在这样的正方形区域满足条件呢?如果一株株去数,每次枚举一个len,不仅需要用O(n^2)的时间去枚举正方形,还要用同样的时间来统计区域内的三叶草数量,复杂度难以承受。

第一个问题,如何在O(1)的时间内求出某个正方形区域内三叶草的数目,显然可以采用二维前缀和来解决。

第二个问题,如何快速的枚举完长度为len的所有正方形,一个个枚举显然要枚举解决10000^2次,前缀和数组也要开这么大。但是可以发现,虽然三叶草的坐标最大可达一万,但是可能出现三叶草位置的总和不过500.于是可以使用离散化,将分散的三叶草映射到有限的区域内来,500*500的复杂度并不难接受。

离散化的操作采用了经典的做法,仅需两步即可完成。

sort(numbers.begin(),numbers.end());
numbers.erase(unique(numbers.begin(),numbers.end()),numbers.end());

 numbers用于存储离散化后的结果。先解释第二行代码,unique函数,并没有真正的删除重复的元素,而是将不重复的元素复制到前面重复元素的位置,比如1223346,使用unique后得到1234646,返回值为不重复元素的后一个位置。于是上面的第二行代码成功的删除了numbers中重复的元素,unique函数一般用于操作排好序的数组,所以需要先进行排序。如此一来,所有三叶草可能出现的位置,就都记录在numbers中了。然后通过get(x)函数二分获取numbers中x的下标,(其实也可以再进行一次逆映射,利用map存储所有的逆映射,就不需要每次都要二分查找下标了)。

前缀和公式:sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];其中sum[i][j]原先存储的是该位置内三叶草的数目。

在已经离散化的numbers中判断是否存在边长为len的区域符合题意时,我们可以从左下角的小正方形枚举起,一步步扩张,直至边长大于len就收缩左下角,具体细节见代码。唯一一点需要注意的是判断长度是否大于len的语句,numbers[x2] - numbers[x1 + 1] + 1 > len,之所以要+1,是因为实际的右上角顶点比(x2,y2)还要多一。

如上图所示,(x1,y1),(x2,y2)是正方形的两个对顶点,之前sum是按照点求前缀和的,但这两点表示的却是这两个黑色的正方形,也就是说sum(x2,y2)-sum(x1,y2) -sum(x2,y1)+sum(x1,y1)求得的是白色正方形区域内的三叶草的数目,要想真正求得以(x1,y1),(x2,y2)为顶点的的正方形区域内的三叶草数目有关x1,y1坐标的部分均需减一。

所以numbers[x2] - numbers[x1 + 1] + 1 > len条件判断的是以(x1+1,y1+1)为左下角的正方形(图中白色区域)中三叶草的数目,倘若要用(x1,y1)直接表示所求正方形的左下角,那么在判断区域内三叶草数目与C的大小关系时,x1,y1均需减一。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int maxn = 1010;
int n,C;
PII points[maxn];
vector<int> numbers;
int sum[maxn][maxn];
int get(int x){//查找x在numbers中的位置
    int l = 0,r = numbers.size() - 1;
    while(l < r){
        int mid = l + r >> 1;
        if(numbers[mid] >= x)   r = mid;
        else    l = mid + 1;
    }
    return r;
}
bool check(int len){//判断是否满足条件
    for(int x1 = 0,x2 = 1;x2 < numbers.size();x2++){
        while(numbers[x2] - numbers[x1 + 1] + 1 > len)  x1++;
        for(int y1 = 0,y2 = 1;y2 < numbers.size();y2++){
            while(numbers[y2] - numbers[y1 + 1] + 1 > len)  y1++;
            if(sum[x2][y2] - sum[x1][y2] - sum[x2][y1] + sum[x1][y1] >= C)  return true;
        }
    }
    return false;
}
int main(){
    cin>>C>>n;
    numbers.push_back(0);//哨兵
    for(int i = 0;i < n;i++){
        int x,y;
        cin>>x>>y;
        points[i] = {x,y};
        numbers.push_back(x);
        numbers.push_back(y);
    }
    sort(numbers.begin(),numbers.end());
    numbers.erase(unique(numbers.begin(),numbers.end()),numbers.end());//离散化
    for(int i = 0;i < n;i++){
        int x = get(points[i].first),y = get(points[i].second);
        sum[x][y] ++;  
    }
    for(int i = 1;i < numbers.size();i++)
        for(int j = 1;j < numbers.size();j++)
            sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];//求前缀和
    int l = 1,r = 10000;
    while(l < r){//二分len
        int mid = l + r >> 1;
        if(check(mid))  r = mid;
        else    l = mid + 1;
    }
    cout<<r<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/89439615
121