题目链接
题目描述
在二维平面上有 个带权值的点
。你需要以原点
为圆心放置一个圆,半径为
。
如果一个点满足 ,则该点被圆覆盖。
你需要找到一个最小的半径 ,使得所有被覆盖的点的权值之和不少于给定的整数
。如果无论半径多大都无法达到权值下限,则输出 -1。
解题思路
这是一个求解满足特定条件的最小值问题,并且该条件具有明显的单调性,因此非常适合使用二分答案的方法。
1. 单调性分析
我们要求的是最小半径 。可以观察到,当半径
增大时,被圆覆盖的点的集合是只增不减的,因此这些点的权值之和也是一个单调非递减的函数。
换句话说,如果一个半径 能够满足“权值和不小于
”的条件,那么任何大于
的半径也一定能满足该条件。这种单调性是使用二分答案的前提。
2. 二分答案框架
我们可以在一个足够大的半径范围内对答案 进行二分查找。
-
check(r)
函数:我们需要一个辅助函数check(r)
,用于判断给定的半径是否可行。
- 该函数的逻辑是:计算所有被半径为
的圆覆盖的点的权值之和。
- 遍历所有
个点,对于每个点
,计算其到原点距离的平方
。
- 为了避免浮点数精度问题,我们比较距离的平方:如果
,则将该点的权值
累加。
- 最后,如果累加的总权值大于或等于
,则
check(r)
返回true
,否则返回false
。
- 该函数的逻辑是:计算所有被半径为
-
二分过程:
- 我们设定一个二分区间,左边界
,右边界
可以取一个足够大的值(例如
,因为坐标最大为
)。
- 在循环中(通常进行固定次数如100次以保证精度),取中点
。
- 如果
check(mid)
为true
,说明半径是一个可行的解,但可能不是最小的。我们记录下这个解,并尝试在更小的范围
中寻找更优解。
- 如果
check(mid)
为false
,说明半径太小,必须增大。我们在范围
中继续寻找。
- 我们设定一个二分区间,左边界
-
初始判断:在开始二分前,应先计算所有点的权值总和。如果总和小于
,则不可能满足条件,直接输出 -1。
代码
#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
using namespace std;
struct Point {
long long x, y, v;
};
// 检查半径 r 是否满足条件
bool check(double r, long long S, int n, const vector<Point>& points) {
long long current_weight = 0;
double r_sq = r * r;
for (int i = 0; i < n; ++i) {
long long d_sq = points[i].x * points[i].x + points[i].y * points[i].y;
if (d_sq <= r_sq) {
current_weight += points[i].v;
}
}
return current_weight >= S;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
long long S;
cin >> n >> S;
vector<Point> points(n);
long long total_weight = 0;
for (int i = 0; i < n; ++i) {
cin >> points[i].x >> points[i].y >> points[i].v;
total_weight += points[i].v;
}
if (total_weight < S) {
cout << -1 << endl;
return 0;
}
double left = 0.0, right = 2e9; // 一个足够大的半径上界
// 进行100次二分以保证精度
for(int i = 0; i < 100; ++i) {
double mid = left + (right - left) / 2.0;
if (check(mid, S, n, points)) {
right = mid;
} else {
left = mid;
}
}
cout << fixed << setprecision(10) << right << endl;
return 0;
}
import java.util.Scanner;
public class Main {
static class Point {
long x, y, v;
}
// 检查半径 r 是否满足条件
private static boolean check(double r, long S, int n, Point[] points) {
long currentWeight = 0;
double rSq = r * r;
for (int i = 0; i < n; i++) {
long dSq = points[i].x * points[i].x + points[i].y * points[i].y;
if (dSq <= rSq) {
currentWeight += points[i].v;
}
}
return currentWeight >= S;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long S = sc.nextLong();
Point[] points = new Point[n];
long totalWeight = 0;
for (int i = 0; i < n; i++) {
points[i] = new Point();
points[i].x = sc.nextLong();
points[i].y = sc.nextLong();
points[i].v = sc.nextLong();
totalWeight += points[i].v;
}
if (totalWeight < S) {
System.out.println(-1);
return;
}
double left = 0.0, right = 2e9; // 一个足够大的半径上界
// 进行100次二分以保证精度
for (int i = 0; i < 100; i++) {
double mid = left + (right - left) / 2.0;
if (check(mid, S, n, points)) {
right = mid;
} else {
left = mid;
}
}
System.out.printf("%.10f\n", right);
}
}
import math
def check(r, S, n, points):
current_weight = 0
r_sq = r * r
for x, y, v in points:
d_sq = x * x + y * y
if d_sq <= r_sq:
current_weight += v
return current_weight >= S
def main():
n, S = map(int, input().split())
points = []
total_weight = 0
for _ in range(n):
x, y, v = map(int, input().split())
points.append((x, y, v))
total_weight += v
if total_weight < S:
print(-1)
return
left, right = 0.0, 2e9 # 一个足够大的半径上界
# 进行100次二分以保证精度
for _ in range(100):
mid = (left + right) / 2.0
if check(mid, S, n, points):
right = mid
else:
left = mid
print(f"{right:.10f}")
if __name__ == "__main__":
main()
算法及复杂度
- 算法:实数域二分答案。
- 时间复杂度:
,其中
是点的数量,
是二分次数(为了保证精度,通常取一个固定的值,如 100)。
check
函数每次调用需要的时间。
- 空间复杂度:
,用于存储
个点的信息。