#P3330. 第3题-最小半径
-
3000ms
Tried: 23
Accepted: 6
Difficulty: 8
所属公司 :
科大讯飞
时间 :2025年8月2日
-
算法标签>二分算法
第3题-最小半径
解题思路
二分半径 + 扫描线
-
思路概览:将答案 r 用二分搜索,在每个候选半径上判断能否在 x 轴或 y 轴上放置圆心,使得被覆盖点数 ≥k,其中 k=⌈n/2⌉。
-
可行性检查:
-
对给定半径 r,分别在两条轴上判断:
- 横轴情况(axis=0):圆心 (c,0) 覆盖点 (xi,yi) 的条件是 ∣yi∣≤r 且 ∣c−xi∣≤r2−yi2,从而得到在 c 轴上的区间 [xi−di,xi+di]。
- 纵轴情况(axis=1):圆心 (0,c) 覆盖点条件是 ∣xi∣≤r 且 ∣c−yi∣≤r2−xi2,区间为 [yi−di,yi+di]。
-
将所有区间的左右端点作为事件:左端记 +1,右端记 −1,对事件按坐标升序(同点时“+1”先于“-1”)排序并扫描,记录最大同时覆盖数。若某一轴上最大覆盖数 ≥k,则该 r 可行。
-
-
二分搜索:
- 初始区间 [0,R],R 可设为 (2⋅105)2+(2⋅105)2≈3×105。
- 重复约 60 次,直到区间长度 ≤10−6。
复杂度分析
- 每次可行性检查:遍历 n 点生成最多 2n 事件,排序 O(nlogn),扫描 O(n),共 O(nlogn)。
- 二分约 60 轮,总体 O(60,nlogn)=O(nlogn),可在 n≤105 下通过。
代码实现
Python
import sys, math
def check(r, pts, k):
# axis=0 横轴;axis=1 纵轴
def scan(axis):
ev = []
for x, y in pts:
# 按轴判断
if axis == 0:
if abs(y) > r: continue
d = math.sqrt(r*r - y*y)
L, R = x - d, x + d
else:
if abs(x) > r: continue
d = math.sqrt(r*r - x*x)
L, R = y - d, y + d
ev.append((L, 1))
ev.append((R, -1))
if len(ev) < 2*k:
return False
ev.sort(key=lambda e: (e[0], -e[1]))
cnt = 0
for _, v in ev:
cnt += v
if cnt >= k:
return True
return False
# 只要任一轴可行即返回 True
return scan(0) or scan(1)
def main():
data = sys.stdin.read().split()
n = int(data[0])
pts = [(float(data[i]), float(data[i+1])) for i in range(1, 2*n, 2)]
k = (n + 1) // 2
lo, hi = 0.0, 3e5
for _ in range(60):
mid = (lo + hi) / 2
if check(mid, pts, k):
hi = mid
else:
lo = mid
print("{:.6f}".format(hi))
if __name__ == "__main__":
main()
Java
import java.io.*;
import java.util.*;
public class Main {
static int n, k;
static double[] xs, ys;
// 判断半径 r 是否可行
static boolean check(double r) {
// scan 对某条轴(axis=0 横,1 纵)做事件扫描
for (int axis = 0; axis < 2; axis++) {
List<double[]> ev = new ArrayList<>();
for (int i = 0; i < n; i++) {
double d0 = axis == 0 ? Math.abs(ys[i]) : Math.abs(xs[i]);
if (d0 > r) continue;
double d = Math.sqrt(r*r - d0*d0);
double mid = axis == 0 ? xs[i] : ys[i];
ev.add(new double[]{mid - d, 1});
ev.add(new double[]{mid + d, -1});
}
if (ev.size() < 2*k) continue;
ev.sort((a, b) -> {
if (a[0] != b[0]) return Double.compare(a[0], b[0]);
return Double.compare(b[1], a[1]);
});
int cnt = 0;
for (double[] e : ev) {
cnt += (int)e[1];
if (cnt >= k) return true;
}
}
return false;
}
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(in.readLine().trim());
xs = new double[n];
ys = new double[n];
for (int i = 0; i < n; i++) {
String[] sp = in.readLine().split("\\s+");
xs[i] = Double.parseDouble(sp[0]);
ys[i] = Double.parseDouble(sp[1]);
}
k = (n + 1) / 2;
double lo = 0, hi = 3e5;
for (int it = 0; it < 60; it++) {
double mid = (lo + hi) / 2;
if (check(mid)) hi = mid;
else lo = mid;
}
System.out.printf("%.6f\n", hi);
}
}
C++
#include <bits/stdc++.h>
using namespace std;
int n, k;
vector<double> xs, ys;
// 判断给定半径 r 是否能在任一轴上覆盖 >=k 个点
bool check(double r) {
for (int axis = 0; axis < 2; axis++) {
vector<pair<double,int>> ev;
for (int i = 0; i < n; i++) {
double d0 = axis == 0 ? fabs(ys[i]) : fabs(xs[i]);
if (d0 > r) continue;
double d = sqrt(r*r - d0*d0);
double mid = axis == 0 ? xs[i] : ys[i];
ev.emplace_back(mid - d, +1);
ev.emplace_back(mid + d, -1);
}
if ((int)ev.size() < 2*k) continue;
sort(ev.begin(), ev.end(), [](auto &a, auto &b){
if (a.first != b.first) return a.first < b.first;
return a.second > b.second;
});
int cnt = 0;
for (auto &e : ev) {
cnt += e.second;
if (cnt >= k) return true;
}
}
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
xs.resize(n); ys.resize(n);
for (int i = 0; i < n; i++) {
cin >> xs[i] >> ys[i];
}
k = (n + 1) / 2;
double lo = 0.0, hi = 3e5;
for (int it = 0; it < 60; it++) {
double mid = (lo + hi) / 2;
if (check(mid)) hi = mid;
else lo = mid;
}
cout << fixed << setprecision(6) << hi << "\n";
return 0;
}
题目内容
在平面上给定n个二维点,其坐标分别为(xi,yi)(1≤i≤n)。
现在需要在坐标平面上以某一点C为圆心画一个圆,且使得该圆能够覆盖不少于该圆心必须位于坐标轴上。
请你找到一个最小的半径r,使得该圆能过覆盖不少于[n/2]个给定点,并输出这个最小半径。
【名词解释】
-
坐标轴:包含x轴和y轴。轴表示形如(x,0)的所有点;y轴表示形如(0,y)的所有点。
-
覆盖:若点P到圆心C的欧氏距离不超过圆的半径,则称该圆覆盖点P。
输入描述
第一行输入一个整数n(2≤n≤105),表示点的数量。
接下来n行,每行输入两个整数xi,yi(−105≤xi,yi≤105),表示第i个点的坐标。
输出描述
输出一个实数,表示能够覆盖至少[n/2]个给定点的、以坐标轴上某点为圆心的最小圆的半径。
由于实数计算可能产生误差,当误差的量级不超过10−6时,您的答案都将被接受。具体来说,设您的输出为a,标准答案为b,当且仅当max(1,∣b∣)∣a−b∣≤10−6时,您的答案将被接受。
输入
2
0 0
3 4
输出
0.0000000000
说明
在这个样例中,n=2,[n/2]=1;我们可以选择圆心(0,0),半径0,覆盖点(0,0)。
样例2
输入
4
1 0
2 0
3 0
100 100
输出
0.5000000000
说明
在这个样例中,n=4,[n/2]=2;我们可以选择圆心(1.5,0),半径0.5,即可覆盖点(1,0)和(2,0)。