JZOJ 6489【GDOI2020模拟02.29】守卫疆域guard (最小k覆盖圆,二分+扫描线)

Description:

(n<=500)

题解:

先想一个暴力的做法。

我们知道最小覆盖圆一定是原点集 中的三个点形成的圆或者是以原点集 中的两点为直径的圆。

同样的道理,最小k覆盖圆肯定也满足这个性质。

那么枚举这些圆,都判一下即可,这是(O(n^4))的。

对于这题,可以先二分答案。

因为圆一定至少经过一个点,所以枚举这个点x,考虑圆心绕着它旋转了一圈。

对于每一个点y,当圆心和x的几角在一个区间时,y会在圆里。

利用余弦定理去计算这个几角区间,然后扫描线看有没有被覆盖不少于k次的区间即可。

复杂度:(O(n^2~log~V~log~n))

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i <  B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define ll long long
#define pp printf
#define hh pp("
")
using namespace std;

#define db double

struct P {
	db x, y;	
	P(){}
	P(db _x, db _y) { x = _x, y = _y;}
};

const db eps = 1e-8;

P operator - (P a, P b) { return P(a.x - b.x, a.y - b.y);}
db len(P a) { return sqrt(a.x * a.x + a.y * a.y);}
db len2(P a) { return a.x * a.x + a.y * a.y;}
bool operator < (P a, P b) { return a.x < b.x;}
db ang(P a) { return atan2(a.y, a.x);}

const int N = 505;

const db pi = acos(-1);
int n, k;
P a[N], d[N * 2]; int d0;

int pd(db r) {
	if(r < 0) return 0;
	fo(i, 1, n) {
		d0 = 0;
		int cnt = 1;
		fo(j, 1, n) if(i != j) {
			if(len2(a[j] - a[i]) < eps) {
				cnt ++;
			} else
			if(len(a[j] - a[i]) <= 2 * r) {
				db w = ang(a[j] - a[i]);
				db t = (pi - acos((1 - len2(a[j] - a[i]) / (2 * r * r)))) / 2;
				db l = w - t, r = w + t;
				if(l <= r) {
					d[++ d0] = P(l, 1);
					d[++ d0] = P(r, -1);
				} else {
					cnt ++;
					d[++ d0] = P(r, -1);
					d[++ d0] = P(l, 1);
				}
			}
		}
		sort(d + 1, d + d0 + 1);
		fo(j, 1, d0) {
			cnt += (int) d[j].y;
			if(cnt >= k) return 1;
		}
	}
	return 0;
}

int main() {
	freopen("guard.in", "r", stdin);
	freopen("guard.out", "w", stdout);
	scanf("%d %d", &n, &k);
	fo(i, 1, n) scanf("%lf %lf", &a[i].x, &a[i].y);
	db ans = 1e4;
	for(db p = 1e4; p > 1e-5; p /= 2)
		if(pd(ans - p)) ans -= p;
	pp("%.10lf
", ans);
}
原文地址:https://www.cnblogs.com/coldchair/p/12388629.html