题意
平面上有\(n\)个点,你可以画\(m\)条直线,使得每个点和原点的交线和\(m\)条直线至少有一个相交点。问离原点最近的直线于原点距离的最大值。
题解
显然应该先二分最近直线离原点距离。
可以把这个距离看成半径,画一个圆。
如果我们把点从2D平面上映射到一个序列上(或者说一个环状序列),我们可以把辐角作为偏序。
那么这样,如果用一条线能把两个点\((i, j)\)都保护住,在圆上的表现就是两个点和两个点在圆上的切点的关系,表现到序列上即
\[ arg_i \leq arg_{tg_j} \leq arg_{tg_i} \leq arg_j \]
其中\(tg_i\)和\(tg_j\)是满足条件的\(i\),\(j\)的某个关于圆的切点(一般一个点关于圆的切点有两个)。
并且这只是一种合法的情况。\(i\)和\(j\)位置互换也是可以的。
所以,我们就得到了一个思路:过每个点作圆的切线,设切点的俯角分别为\(arg_{l_i}\),\(arg_{r_i}\),那么这个点就可以映射到一个环状序列上的一段区间\(arg_{l_i}\),\(arg_{r_i}\)。
现在的问题是,我们要取出几个的区间,要求覆盖整个环状序列,并且区间数最少(其实区间数最少就可以等价于所需直线最少)
这个问题对于环状序列和线性序列本质没有区别,破环为链再倍长即可解决这个问题。考虑序列上如何解决。
设\(f_{i, j}\)为在第\(i\)个位置开始,向后取了\(2 ^ j\)个区间,铺满的极大区间为\([i, f_{i, j}]\)(不考虑\(i\)之前的位置)。
然后可以\(\mathcal O(n)\)预处理出\(f_{i, 0}\),然后再倍增可以得到\(f_{i, j}\)。
对于一个序列,只要判断从首位开始,取了\(m\)个区间,是否能铺满\([1, L]\)。
对于一个环状序列,只要枚举从哪一个位置开始,做一次序列上的判断即可。
复杂度\(\mathcal O(n \log n \log V)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N = 2e5 + 10, H = 20;
const db pi = acos(-1.0);
int n, m, L, f[H][N];
struct P {
int x, y;
db arg, dis;
} a[N];
struct seg {
db l, r;
bool operator < (const seg &o) const {
return r == o.r ? l < o.l : r < o.r;
}
} s[N];
bool check (db r) {
for (int i = 1; i <= n; ++i) {
if (r > a[i].dis) {
return 0;
}
db dlta = acos(r / a[i].dis);
s[i] = (seg) {a[i].arg - dlta, a[i].arg + dlta};
if (s[i].r > pi) {
s[i].l -= pi * 2;
s[i].r -= pi * 2;
}
}
sort(s + 1, s + n + 1);
for (int i = 1; i <= n; ++i) {
s[i + n] = (seg) {s[i].l + pi * 2, s[i].r + pi * 2};
}
f[0][L + 1] = L + 1;
for (int i = 1, j = 0; i <= L; f[0][i] = j, ++i) {
for (j = (j <= i ? i + 1 : j); j <= L && s[j].l <= s[i].r; ++j);
}
for (int i = 1; i < H; ++i) {
for (int j = 1; j <= L + 1; ++j) {
f[i][j] = f[i - 1][f[i - 1][j]];
}
}
for (int i = 1, j; i <= n; i++) {
j = i;
for (int k = H - 1; ~k; --k) {
if (m >> k & 1) {
j = f[k][j];
}
}
if (j >= i + n) {
return 1;
}
}
return 0;
}
int main () {
scanf("%d%d", &n, &m), L = n << 1;
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &a[i].x, &a[i].y);
a[i].arg = atan2(a[i].y, a[i].x);
a[i].dis = sqrtl(1ll * a[i].x * a[i].x + 1ll * a[i].y * a[i].y);
}
db l = 0, r = a[1].dis, mid;
while (r - l >= 1e-10) {
mid = (l + r) / 2;
check(mid) ? l = mid : r = mid;
}
printf("%.11lf\n", l);
return 0;
}