题目描述
农夫约翰希望为他的奶牛们建立一个畜栏。
这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含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
蒟蒻的我在yxc的思想指导下写的第一个题解
离散化 + 二维数组前缀和 + 二分查找
坐标的范围在1 ~ 10000,如果直接开二维数组遍历显然不现实,再看N的范围最大是500,那么我们就可以将坐标离散化后存在一个数组里,用这个数组来进行前缀和运算的操作,就会大大降低时间复杂度。
但要注意的是,这珠作死的草会在 1 × 1 的方格里,所以在算正方形边长的时候,需要在 x2 - x1 之后 再加上一个单位长度,y1,y2同理(x1,y1,x2,y2是当前选取的正方形的左上角和右下角的坐标)。而在算边长的时候,需要查找离散化之前的坐标来进行运算。最后需要提的是,这珠草可以正好落在正方形边长上,所以在最后计算一个正方形范围内的草的数目的时候公式应该为
sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1]
不同于之前的激光炸弹,激光炸弹因为不能算边界,所以不需要-1。
剩下的,就请自己看代码吧。第一次写题解,写的不好请多原谅!!!
同时也请各位大佬指点!!!
C++ 代码
# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;
typedef pair<int, int> PII;
int N, C;
int sum[1010][1010]; //前缀和 因为x跟y最多会有1000个不同的坐标,所以开大一点避免溢出
vector<PII> points; //点的坐标
vector<int> numbers; //离散化结果
int get_id(int n)
{
return lower_bound(numbers.begin(), numbers.end(), n) - numbers.begin();
}
bool check(int len)
{
for (int x1 = 1, x2 = 1; x2 < numbers.size(); x2++) {
while (numbers[x2] - numbers[x1] + 1 > len) x1++;
for (int y1 = 1, y2 = 1; y2 < numbers.size(); y2++) {
while (numbers[y2] - numbers[y1] + 1 > len) y1++;
if (sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1] >= C) return true;
//因为包括边界,所以左上角坐标需要-1
}
}
return false;
}
int main()
{
cin >> C >> N;
numbers.push_back(0);
for (int i = 0; i < N; i++) {
int x, y;
cin >> x >> y;
numbers.push_back(x);
numbers.push_back(y);
points.push_back({ x, 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_id(points[i].first), y = get_id(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) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
return 0;
}
楼主能教教我,为啥离散化的x和y为啥能放在一个数组里么
这里离散化是将需要用到的那些位置离散,并不需要区分是 x 还是 y
%%%
楼主,能解答一下check函数中为什么numbers[x2] - numbers[x1] + 1能代表边长嘛,没太看懂这个
这个是叫二分答案麻大佬
是的。
巨巨 这段代码 跳出while 不就是 num[x2] - num[x1] + 1 <= len吗?
但是 check函数 不是要检验 长度刚好等于len的吗?
将 $check$ 函数理解为:边长最长为 $len$,是否有某块区域内部的三叶草满足要求
$len$ 其实是二分传入的 $mid$
如果 $check$ 为 $true$,那么,所有 $>\ mid$ 的边长返回的都是 $true$,因此,令 $r\ =\ mid$
否则,说明说有 $\leq\ mid$ 的边长返回的都是 $false$,而且,$mid$ 一定不可能是答案,因此,令 $l\ =\ mid\ +\ 1$
那么,最后二分得到的就恰好是那个边界值,也就是要求的最小边长
再说一句(与题无关)
如果最初二分的 $l,\ r$ 中可能不包含答案,用上面的说法举例子的话,就是最初二分的边界 $l,\ r$ 都是返回的 $false$,那么,按照上述的二分方法,最终二分的结果会停在右端点,也就是最初的 $r$ 值上
这种情况下,个人喜欢跳出二分之后,再 $check$ 一下二分出的值来判断(当然,也可以在最初二分的范围边界左右两侧各设置一个哨兵之类的方法来解决)
换句话说,如果 $check$ 检验的是恰好等于 $len$ 的边长栅栏,那么整个区间将不再具有单调性,也就不能用二分了
你好像理解错他的意思了 我来回答一下吧
因为是算边界的 所以长度其实是x2-(x1-1)=len 就是 x2-x1+1=len 所以他就是等于len才退出来的
有可能是长小于宽等于
离散化和哈希 一样吗?
离散化:当数字范围很大,但是数字总量不大的时候,将大的数字用小的数字(可以有序也可以无序)来表示,以达到方便存储的目的(就不需要开那么大空间了)
哈希:根据某个规则,将内容根据规则转化成另一种形式,最常见的应该就是将字符串转换成数字来存储,只要转化规则相同,同一个字符串转化成的数字应该是一样的(如果不考虑冲突的话)。
当然,哈希还是有冲突的概率的(两个字符串转化成同一结果之类的,导致查询等问题),但是根据前人的一些经验和算法,可以将这个冲突概率降到很低。
硬要说的话,离散化可以算是一种规则简单的哈希?
(我也不清楚能不能这么表述hh)(以上纯属个人观点
多谢 巨巨
谢谢大佬!
最坏情况复杂度应该是n^3log10000吧
在 $\mathit {check}$ 函数中,以 $\mathit x_\text 1,\ \mathit x_\text 2$ 为例,$\mathit x_\text 2$ 从 $\text 0$ 枚举到 $\mathit {number}.\mathit {size}$,同样的,$\mathit x_\text 1$ 最多也只会枚举这么多,且随着 $\mathit x_\text 2$ 的变化,$\mathit x_\text 1$ 也只有可能变大,不会变小,也就是说在 $\mathit {for}$ 内部的对于 $\mathit x_\text 1$ 的 $\mathit {while}$ 循环,最多一共只会执行 $\mathit {number}.\mathit {size}$ 次,所以对于横坐标,虽然是一个 $\mathit {for}$ 一个 $\mathit {while}$,但是整体是线性的,同理,对于纵坐标也是一样,所以这个函数是 $\mathit O(\mathit n ^ \text 2)$ 的,外面再套一个二分。总体时间复杂度应该是 $\mathit n ^ \text 2\mathit{log}(\text {10000})$ 吧。
orzorzorz
不过希望能把numbers的正确性说明一下,不然该题解意义不大
大佬,为啥numbers[x2] - numbers[x1]能代表边长长度呀,没太看懂
numbers.push_back(0); 老哥,这一步是为什么要加呢
应该是因为不加的话计算前缀和就要从0开始计算了,这样i = 0 和 j= 0时sum[i - 1][j - 1]还需要额外处理
大概理解了,谢谢
时间复杂度是n^2*log2 10000吗
大概是 n ^ 2 * log n 吧,离散化完之后最多也只剩下 n 个不同的数了吧
应该最多剩下 2n 个不同的数吧
对对,$\text 2\ \cdot\ \mathit n$ 个数,横纵坐标都不一样的时候
离散化完成之后是2 * n 个数,但是这个log10000表示的不是二分长度的时间复杂度吗,应该是和长度有关,而不是和个数有关吧,代码里写的l= 0, r = 10000, 那最后应该还是n^2 * log10000吧。。。。
确实hh,时间太久,没注意到 ̄□ ̄||
# %%%