问题分析
在一个长方形框内,有 N 个相异的点。在每个点上依次放置一个油滴,油滴会扩展直到接触到其他油滴或框子的边界。要求找出放置顺序,使得所有油滴占据的总面积最大(即剩余空间最小)。油滴是圆形且不会融合,必须等一个油滴完全扩展后才能放置下一个。
关键点:
- 每个油滴的半径取决于放置时的环境:到边界的最小距离和到已放置油滴的距离。
- 先放置的油滴会限制后放置油滴的半径。
- 为了最大化总面积,应优先放置那些在无约束时能扩展得更大的点(即到边界距离较大的点),因为它们在无约束时能占据更大面积,即使覆盖后续点导致其半径为0,总面积也可能更大。
算法思路
- 计算每个点的初始最大半径:对于每个点,计算其到长方形四条边界的最小距离(即单独放置时能扩展的最大半径)。
- 按初始半径降序排序:优先放置初始半径大的点,使其在无其他油滴约束时达到最大可能半径。
- 依次放置油滴并计算实际半径:
- 对于当前点,其实际半径受限于:
- 初始最大半径(到边界的最小距离)。
- 到每个已放置油滴的距离减去该油滴的半径(确保不重叠)。
- 实际半径取上述所有约束的最小值,若为负则置0(表示被覆盖)。
- 对于当前点,其实际半径受限于:
- 计算总面积和剩余空间:
- 所有油滴面积之和 = Σ(π * r_i²)。
- 长方形面积 = (x_right - x_left) * (y_top - y_bottom)。
- 剩余空间 = 长方形面积 - 油滴总面积(四舍五入取整)。
优化
- 提前终止:在计算当前点的实际半径时,若半径已降至0或负,可提前终止内层循环(后续约束不会增大半径)。
- 浮点误差处理:剩余空间若为负(因浮点误差),置0后再四舍五入。
复杂度
- 时间复杂度:O(N²),其中 N 为点数。排序 O(N log N),放置油滴时每点需检查所有已放置点。
- 空间复杂度:O(N),存储点和半径。
代码实现
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
struct Point {
double x, y;
double r0;
int id;
};
int main() {
int N;
cin >> N;
double x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
double x_left = min(x1, x2);
double x_right = max(x1, x2);
double y_bottom = min(y1, y2);
double y_top = max(y1, y2);
vector<Point> pts;
for (int i = 0; i < N; i++) {
double x, y;
cin >> x >> y;
double d1 = x - x_left;
double d2 = x_right - x;
double d3 = y - y_bottom;
double d4 = y_top - y;
double r0 = min(min(d1, d2), min(d3, d4));
pts.push_back({x, y, r0, i});
}
sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) {
return a.r0 > b.r0;
});
vector<double> radius(N, 0.0);
for (int i = 0; i < N; i++) {
double r = pts[i].r0;
for (int j = 0; j < i; j++) {
double dx = pts[i].x - pts[j].x;
double dy = pts[i].y - pts[j].y;
double d = sqrt(dx * dx + dy * dy);
double candidate = d - radius[pts[j].id];
if (candidate < r) {
r = candidate;
}
if (r <= 0) {
break;
}
}
if (r < 0) {
r = 0;
}
radius[pts[i].id] = r;
}
const double PI = acos(-1.0);
double total_circle_area = 0.0;
for (int i = 0; i < N; i++) {
total_circle_area += PI * radius[i] * radius[i];
}
double rect_area = (x_right - x_left) * (y_top - y_bottom);
double remaining = rect_area - total_circle_area;
if (remaining < 0) {
remaining = 0;
}
long ans = lround(remaining);
cout << ans << endl;
return 0;
}
放置油滴:
- 遍历排序后的点,对每个点计算实际半径:
- 初始为
r0。 - 遍历所有已放置点,更新半径为到已放置点距离减去其半径的最小值。
- 若半径降至0或负,提前终止内层循环。
- 初始为
- 最终半径不小于0。
面积计算:
- 油滴总面积 = Σ(π * r²)。
- 剩余空间 = 长方形面积 - 油滴总面积。
- 处理浮点误差(剩余空间非负),四舍五入输出。

京公网安备 11010502036488号