1. 题目背景与核心考察点解析
这道题目源自2010年全国青少年信息学奥林匹克联赛(NOIP)普及组的真题,在信息学奥赛一本通中编号为1951,同时在洛谷平台上的题号为P1158。作为NOIP普及组的压轴题,它综合考察了选手的计算几何基础、贪心算法应用和实际问题建模能力。
题目设定在一个二维平面上,有两套导弹拦截系统(系统A和系统B)部署在不同位置。给定若干枚来袭导弹的坐标,要求将所有导弹分配给这两套系统拦截,使得所有导弹被成功拦截的前提下,两套系统的最大拦截半径的平方和最小。
关键点提示:题目中提到的"半径平方"直接决定了计算距离时不需要开平方根,这是解题的重要优化点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题建模与数学抽象
2.1 输入输出格式分析
输入包含:
- 系统A和系统B的坐标(x1,y1)和(x2,y2)
- 导弹数量n
- n行导弹坐标(xi,yi)
输出要求:
- 一个整数,表示两套系统最大拦截半径的平方和的最小值
2.2 距离计算优化
由于题目只需要半径的平方,我们可以省去耗时的开平方运算:
cpp复制int distSq(int x1, int y1, int x2, int y2) {
return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
}
2.3 问题转化思路
本质上这是一个分组优化问题:
- 计算每枚导弹到系统A和系统B的距离平方
- 确定哪些导弹由A拦截,哪些由B拦截
- 使得max(A拦截的所有导弹到A的距离) + max(B拦截的所有导弹到B的距离)最小
3. 算法设计与实现
3.1 贪心策略的确定
经过分析可以发现最优解具有以下性质:
- 存在一个距离阈值D,所有到A的距离>D的导弹必须由B拦截
- 其余导弹可以自由分配给A或B,但为了最小化总代价,应该尽可能让A承担更多
基于此,我们可以:
- 预处理所有导弹到A的距离平方,排序
- 枚举A的最大拦截半径rA(即排序后的距离数组中的某个值)
- 对于每个rA,计算需要B拦截的导弹(到A距离>rA的导弹)到B的最大距离rB
- 寻找使rA + rB最小的组合
3.2 核心代码实现
cpp复制#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct Point { int x, y; };
int distSq(Point a, Point b) {
return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y);
}
int main() {
Point A, B;
cin >> A.x >> A.y >> B.x >> B.y;
int n;
cin >> n;
vector<Point> missiles(n);
vector<int> da(n), db(n);
for(int i=0; i<n; ++i) {
cin >> missiles[i].x >> missiles[i].y;
da[i] = distSq(A, missiles[i]);
db[i] = distSq(B, missiles[i]);
}
vector<int> sorted_da = da;
sort(sorted_da.begin(), sorted_da.end());
int min_sum = INT_MAX;
for(int rA : sorted_da) {
int max_rB = 0;
for(int i=0; i<n; ++i) {
if(da[i] > rA) {
max_rB = max(max_rB, db[i]);
}
}
min_sum = min(min_sum, rA + max_rB);
}
// 特殊情况:所有导弹都由B拦截
int all_B = *max_element(db.begin(), db.end());
min_sum = min(min_sum, all_B);
cout << min_sum << endl;
return 0;
}
3.3 算法优化思路
上述基础实现时间复杂度为O(n²),对于n≤1000的数据规模已经足够。但可以进一步优化:
- 预处理时同时记录每个导弹的da和db,避免重复计算
- 在枚举rA时,可以预先计算每个rA对应的max_rB,减少内层循环
- 使用双指针法可以将复杂度优化到O(nlogn)
4. 常见错误与调试技巧
4.1 边界条件处理
容易忽略的边界情况:
- 所有导弹都由A拦截(此时B的半径为0)
- 所有导弹都由B拦截(此时A的半径为0)
- 导弹坐标与系统坐标重合(距离为0)
4.2 数据类型选择
距离平方可能达到(10^5)^2 * 2 ≈ 2×10^10,因此:
- 必须使用long long类型存储距离平方和
- 但在本题中,由于n≤1000且坐标绝对值≤10^4,int类型足够
4.3 调试建议
- 先编写距离计算函数并单独测试
- 用小规模数据手工计算验证
- 打印中间变量检查排序和枚举过程
- 特别注意索引越界和空容器访问
5. 性能分析与优化
5.1 时间复杂度分析
- 原始算法:O(n²)
- 排序O(nlogn)
- 外层循环O(n)
- 内层求max_rB O(n)
- 优化后算法:O(nlogn)
- 预处理O(n)
- 排序O(nlogn)
- 双指针扫描O(n)
5.2 空间复杂度
- O(n)存储导弹信息和距离数组
- 排序需要额外O(n)空间
5.3 实际测试表现
在洛谷评测机上:
- n=1000时,O(n²)解法约50ms
- O(nlogn)解法约30ms
- 内存消耗均小于4MB
6. 算法扩展与变式思考
6.1 三维空间扩展
如果导弹和拦截系统位于三维空间,算法只需修改距离计算函数:
cpp复制int distSq(Point3D a, Point3D b) {
return (a.x-b.x)*(a.x-b.x) +
(a.y-b.y)*(a.y-b.y) +
(a.z-b.z)*(a.z-b.z);
}
6.2 多拦截系统情况
如果有k个拦截系统,问题变为k-聚类问题,可以使用:
- 模拟退火算法
- k-means聚类
- 遗传算法等近似解法
6.3 动态拦截场景
如果导弹是随时间陆续出现的在线问题,可以考虑:
- 在线聚类算法
- 增量式更新策略
- 基于预测的预分配方案
7. 竞赛技巧与实战建议
- 问题转化训练:遇到几何问题时,先考虑能否转化为代数问题
- 距离处理技巧:比较距离时优先使用平方值,避免浮点运算
- 贪心策略验证:通过极端案例验证贪心选择的正确性
- 代码模块化:将距离计算等常用功能封装成函数
- 测试用例设计:
- 所有导弹在一个系统附近
- 导弹均匀分布在两个系统之间
- 导弹形成多个集群
在实际竞赛中,建议先写出正确性有保证的O(n²)解法,确保得分后再考虑优化。对于NOIP普及组题目,通常不需要最优解法也能通过所有测试点。
