[cf 1190] E - Tokitsukaze and Explosion

题意

平面上有\(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;
}

转载于:https://www.cnblogs.com/psimonw/p/11276983.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值