Boundary - 题解
标签与难度
标签: 计算几何, 枚举, 哈希表, map, 数学, 浮点数精度 难度: 1800
题目大意喵~
你好呀,指挥官!这道题是说,在一个二维平面上,我们有 个给定的点,还有一个特殊的老朋友——原点 ,喵~。我们需要找到一个神奇的圆,这个圆的边界(也就是圆周)必须经过原点。在所有满足这个条件的圆里面,我们要找出那个在其边界上包含了最多给定点的圆,并告诉本喵这个最多的点的数量是多少,的说。
简单来说:
- 输入: 一个整数 ,和 个点的坐标 。
- 目标: 找一个过原点 的圆。
- 要求: 这个圆的圆周上要经过尽可能多的给定点。
- 输出: 这个最大数量。
举个栗子,如果一个圆经过了原点,还经过了给定的 这三个点,那么它就包含了 3 个点。我们要找到所有可能的圆里,这个数量的最大值,呐。
解题思路分析
喵哈哈,一道计算几何题呢!看起来有点复杂,但别怕,跟着本喵的思路一步步来,很快就能抓住它的尾巴啦!
核心洞察:圆是怎么确定的?
我们都知道,三个不共线的点可以唯一确定一个圆,对吧?题目里有一个非常重要的线索:所有我们考虑的圆都必须经过原点 。这可真是个天大的好消息喵!这意味着,确定一个候选圆,我们已经有了一个固定的点 ,只需要再找两个来自输入点的 和 就行啦!
只要 这三个点不共线,它们就能唯一确定一个圆。
从暴力到巧妙的优化
一个最直接的想法是:
- 枚举两个点 和 。
- 用 这三个点确定一个圆。
- 再检查其他所有点 是不是也恰好在这个圆上。
这个方法需要三层循环,复杂度是 。对于 高达 2000 的情况,这肯定会超时,跑得比本喵追逗猫棒还慢,不可取!
我们需要更快的办法!不如换个角度思考,喵~?
我们可以固定一个点 ,然后考虑所有经过原点 和 的圆。这样的圆有很多,但如果我们再随便拉一个点 进来,那么圆 (经过 )就被确定下来了。
这里的关键点来了,注意听哦!假设除了 之外,还有另一个点 也和 在同一个圆上。那么,由 确定的圆,和由 确定的圆,一定是同一个圆!同一个圆,自然就意味着它们有同一个圆心。
Aha! 这就是我们的突破口!
算法“喵”计
我们可以这样做:
- 我们来枚举每一个点 (从 到 ),把它当作我们寻找的“最优圆”上的一个固定点。
- 对于固定的 ,我们再遍历所有在它之后的点 (从 到 ,为了不重复计算)。
- 对于每一对 ,我们计算出那个穿过 的圆的圆心坐标。
- 我们用一个
map(或者unordered_map) 来记录这些圆心出现的次数。map<Point, int> center_counts;。Point是我们自己定义的表示坐标的结构体。 - 如果多个不同的 和固定的 算出了同一个圆心,就说明这些点 和 都在同一个过原点的圆上!
map中那个圆心对应的计数值就会增加。 - 遍历完所有的 后,我们在
center_counts中找到出现次数最多的那个圆心。假设它的次数是max_freq。这意味着,我们找到了一个经过 的圆,它还额外经过了max_freq个其他的点。所以,这个圆总共经过了max_freq + 1个我们给定的点(别忘了 自己呀!)。 - 我们对每个 都这么做一遍,记录下每次得到的
max_freq + 1的最大值。这个全局的最大值,就是我们最终的答案啦!
如果 ,那答案就是 本身,作为特殊情况处理一下就好。对于一般情况,至少有一个点,所以答案最少是 1。
如何计算圆心?
现在只剩下一个数学问题了:已知不共线的三个点 , , ,如何求它们的外接圆圆心 ?
圆心到三个点的距离都相等(都是半径)。利用这个性质,我们可以列出方程:
- 圆心到 的距离的平方:
- 圆心到 的距离的平方:
- 圆心到 的距离的平方:
令它们相等:
展开第一个方程: (方程 A)
同理,对第二个方程展开: (方程 B)
这是一个关于 的二元一次方程组!解这个方程组就能得到圆心坐标啦。 用克莱姆法则或者代入消元法都可以解。解出来的结果是:
分母 正是向量 和 的叉积。如果叉积为 0,说明 三点共线,无法构成一个唯一的圆,这种情况我们要跳过,喵~
好了,理论武装完毕,可以开始写代码啦!
代码实现
这是本喵根据上面的思路,精心重构的一份代码,注释超详细的哦,希望能帮到你,喵~
#include <iostream>
#include <vector>
#include <cmath>
#include <map>
#include <algorithm>
// 为了处理浮点数,我们需要一个很小的数来判断是否为0
const double EPS = 1e-8;
// 定义一个点结构体,让代码更清晰,喵~
struct Point {
double x, y;
// 为了能作为 map 的键,需要重载小于运算符
bool operator<(const Point& other) const {
if (std::fabs(x - other.x) > EPS) {
return x < other.x;
}
return y < other.y;
}
};
int main() {
// 加速一下输入输出,让程序跑得快一点
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n;
std::cin >> n;
// 如果点很少,直接就能知道答案啦
if (n <= 1) {
std::cout << n << std::endl;
return 0;
}
std::vector<Point> points(n);
for (int i = 0; i < n; ++i) {
std::cin >> points[i].x >> points[i].y;
}
// 至少有一个点,所以最少能有一个点在圆上
int max_points_on_circle = 1;
// 外层循环,固定一个点 Pi
for (int i = 0; i < n; ++i) {
// 使用 map 统计由 (O, Pi, Pj) 确定的圆心出现的次数
std::map<Point, int> center_counts;
// 内层循环,遍历 Pi 之后的所有点 Pj
for (int j = i + 1; j < n; ++j) {
double x1 = points[i].x, y1 = points[i].y;
double x2 = points[j].x, y2 = points[j].y;
// 计算叉积,作为分母。如果接近0,说明 O, Pi, Pj 共线
double denominator = 2 * (x1 * y2 - x2 * y1);
if (std::fabs(denominator) < EPS) {
continue; // 共线,跳过,不能确定唯一圆
}
// d1 = x1^2 + y1^2, d2 = x2^2 + y2^2
double d1 = x1 * x1 + y1 * y1;
double d2 = x2 * x2 + y2 * y2;
// 根据公式计算圆心坐标
Point center;
center.x = (d1 * y2 - d2 * y1) / denominator;
center.y = (d2 * x1 - d1 * x2) / denominator;
// 对应圆心的计数加一
center_counts[center]++;
}
// 寻找对于当前固定的 Pi,能构成的圆上最多的点数
int local_max_freq = 0;
for (auto const& [center, count] : center_counts) {
if (count > local_max_freq) {
local_max_freq = count;
}
}
// local_max_freq 是除了 Pi 之外的点数,所以总数要加1
max_points_on_circle = std::max(max_points_on_circle, local_max_freq + 1);
}
std::cout << max_points_on_circle << std::endl;
return 0;
}复杂度分析
时间复杂度: 我们有两层嵌套的循环,外层循环 次,内层循环平均 次,所以循环体部分是 的。在循环内部,我们对
std::map进行了插入和访问操作,map 的单次操作是 的,其中 是 map 中的元素数量, 最大为 。所以总的时间复杂度是 ,呐。如果用 std::unordered_map,平均时间复杂度可以优化到 。空间复杂度: 我们主要的额外空间开销是那个
center_counts的map。在最坏的情况下,对于一个固定的 ,它和其它所有 都可能形成不同的圆心,所以map的大小会达到 。因此,空间复杂度是 。
知识点总结
这道题是计算几何入门的好题目,它教会了我们:
- 转化问题: 将一个看似复杂的“寻找最优圆”问题,通过抓住“三点定圆”和“共圆心”的核心特性,转化为了一个“枚举+计数”的问题。
- 降维打击: 把 的暴力想法优化到了 ,这是算法竞赛中非常重要的思维方式,喵~
- 几何公式: 学习并应用了如何通过三点坐标(特别是其中一点是原点时)求解外接圆圆心的公式。
- 数据结构的应用: 巧妙地使用
map来对非整数(double类型的坐标)的键进行计数,以识别相同的几何对象(圆心)。 - 浮点数处理: 在计算几何中,由于精度问题,直接用
==判断两个浮点数是否相等是危险的。正确的做法是判断它们的差的绝对值是否小于一个极小的数EPS。
希望这篇题解能让你对计算几何更有信心!继续加油哦,指挥官!喵~