题目大意

给了平面上n个点的坐标 求出覆盖其中至少m个点的最小圆半径

看到题目一开始感觉不可做,后来想了想,一个圆该如何确定?三点定圆 所以想到枚举每个圆,然后判断有多少个点在圆中 复杂度 \(O(n ^ 4)\)

\(后来想想这个做法很sb\)

\(后来想到了假设一个极限的圆 极限的意思是有两个点离得非常近,只要略微移动就会导致一个点不在圆上\)

\(也就是最后目标圆圆心一定可由某两点唯一确定(即目标圆的圆弧上一定恰有两个平面点集中的点)\)

于是我们可以 \(\textrm{}C_{n}^{2}\) 的枚举任意两圆

枚举n个点,判断是否在圆上或者圆内(即是否被覆盖)。复杂度\(O(n ^ 3)\)

但事实上有\(O(n ^ 2 log n log\frac{D}{\varepsilon })\)的做法

\(然后注意到了其实圆的半径是单调的,并且上界应该是两个点间的最大距离(显然),证明也很简单\)

于是想到枚举半径 \(O (log n)\),想到能否计算出半径为r的圆最多能覆盖多少个点

考虑以点集 \(S\) 中的每个点为圆心,做半径为r的圆。

这样会产生出若干覆盖区域,如果一个区域被 \(k\) 个圆覆盖,那么以这个区域内的任意一点做半径为 \(r\) 的圆,必定能覆盖中的k个点。

这是因为这个区域到这k个圆心的距离都小于 \(r\) 。所以我们只需要统计每个区域被多少个圆给覆盖。

进一步思考,考虑每个圆心的圆,发现这个圆某块区域的覆盖次数就是等于其所对应的的弧被覆盖的次数

\(于是我们考虑如何统计弧被覆盖的次数\)

10.27校内模拟赛 nested(最小圆覆盖)题解_复杂度

 

我们假设现在有两个圆 \(i 和 j\) 其中A,B分别为两圆的圆心,两圆相交于\(C、D\)两点

于是我们可以先求出线段 \(AB\) 的极角,然后再求出 \(∠DBA\)

就可以求出\(C、D\)两点相对于圆心的极角

然后我们可以按照顺时针方向对每个圆与其他所有圆的交点进行排序

不妨设 \(C\) 点为入点, \(D\) 点为出点 当扫描到C时 次数增加一次 圆\(j\)被覆盖了2次(自己一次和圆\(i\)一次)

当扫描到 \(D\) 时 表示整段弧已经扫完了 这时候次数减少一次

表示圆 \(j\) 没有被除自身外的圆覆盖,这便是整个算法的流程

以每个圆为目标圆,计算其与其他所有圆的最大覆盖次数,复杂度瓶颈在极角排序上,需要\(O(n ^ 2 log n)\)

由于出题人过于毒瘤,\(n = 2000\),期望得分 \(25\)

于是这样的做法还不够优秀

进一步观察该算法,发现我们求出了所有的 \(ri\) ,而我们只要求出 \(ri\) 的最小值

我们可以改进,将整个序列随机打乱,然后将当前最小值 \(ans\) 设为 \(INF\)
接下来我们逐个点考虑。对于每个点,我们可以使用一次检查求出该点的答案是否小于ans,若不小于则不需要求出,否则我们二分求出ri。

这样检查的次数为\(n + L log\frac{D}{\varepsilon}\), 其中 \(L\)\(ri\) 序列期望单调栈长度((\(log n\))级别)

这样总的复杂度就变成了\(O((n + log n log\frac{D}{\varepsilon }) n log n)\) 期望得分 \(100\)




#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 2010;
const double eps = 1e-9;
const double pi = acos(-1.0);
const double INF = 1e9;

#define sqr(x) ((x) * (x))

inline int read() {
    char ch = getchar();
    int f = 0, x = 0;
    while (ch > '9' || ch < '0') f |= (ch == '-'), ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch - '0'), ch = getchar();
    if (f)
        x = -x;
    return x;
}

struct point {
    double x, y;
    double friend dis(const point &a, const point &b) { return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y)); }
} p[2010];
struct alpha {
    double v;
    bool flag;
    bool friend operator<(const alpha &a, const alpha &b) { return a.v < b.v; }
} alp[N * N];

int n, m;
int solve(int num, double R) {
    int ans = 0;
    int t = 0;
    for (register int j = 0; j < n; j++) {
        if (num == j)
            continue;
        double theta, phi, D;
        D = dis(p[num], p[j]);
        if (2.0 * R < D)
            continue;
        theta = atan2(p[j].y - p[num].y, p[j].x - p[num].x);
        if (theta < 0)
            theta += 2 * pi;
        phi = acos(D / (2.0 * R));
        alp[t].v = theta - phi + 2 * pi;
        alp[t].flag = true;
        alp[t + 1].v = theta + phi + 2 * pi;
        alp[t + 1].flag = false;
        t += 2;
    }
    sort(alp, alp + t);
    int res = 0;
    for (register int j = 0; j < t; j++) {
        if (alp[j].flag)
            res++;
        else
            res--;
        if (res > ans)
            ans = res;
    }
    if (ans + 1 >= m)
        return true;
    return false;
}
int main() {
    freopen("nested.in", "r", stdin);
    freopen("nested.out", "w", stdout);
    n = read(), m = read();
    for (register int i = 0; i < n; ++i) scanf("%lf%lf", &p[i].x, &p[i].y);
    random_shuffle(p, p + n);
    double ans = INF;
    for (int i = 0; i < n; ++i) {
        if (solve(i, ans)) {
            double l = 0, r = INF;
            while (r - l > eps) {
                double mid = (l + r) / 2;
                if (solve(i, mid))
                    r = mid, ans = mid;
                else
                    l = mid;
            }
        }
    }
    printf("%lf\n", ans);

    return 0;
}