P3897 [湖南集训] Crazy Rabbit

发布时间 2023-12-02 19:53:13作者: Hanx16Msgr

[湖南集训] Crazy Rabbit

Luogu P3897

题目描述

兔子们决定在自己的城堡里安排一些士兵进行防守。

给出 \(n\) 个点的坐标,和城堡里一个圆心在原点的圆形的障碍,兔子们希望从中选出 \(k\) 个兔子,使得它们两两所在的直线都不与圆相交。

兔子们希望知道最多能选出多少兔子。

  • 对于 \(10\%\) 的数据,保证 \(1\leq n\leq 20\)
  • 对于 \(30\%\) 的数据,保证 \(1\leq n\leq 100\)
  • 对于 \(100\%\) 的数据,保证 \(1\leq n\leq 2000\)\(1\leq r,x_i,y_i \leq 5000\)

Solution

考虑对于每个点作出对圆的两条切线,那么每一个点都可以对应成圆上的一个区间。具体的区间可以使用 atan2acos 计算出来。

考虑这一个转化有什么作用。观察发现,如果两个点可以同时选,那么这两个点对应的区间一定是相交且不是包含关系。容易证明这是充要条件。

那么问题转化成为了选出尽可能多的区间,使得所有区间两两相交且不包含。观察数据范围发现可以枚举一个起点区间,然后剩下的计算只要能在 \(\mathcal O(n\log n)\) 内完成都是可行的。

考虑枚举起点区间,然后将所有左端点在起点区间内,右端点不在起点区间内的区间取出。将这些区间按照左端点排序,那么因为不能存在包含关系,那么此时能选择的区间的右端点应该是递增的。也就是选定了起点区间并将其他区间按照上述规则排序后只需要求右端点的 LIS 即可算出答案。那么 \(\mathcal O(n^2\log n)\) 的做法显然。

由于原问题的区间是在一个圆上的,因此需要确定一个分割点。考虑跨过分割点的区间如何处理。会发现如果将这个区间变成其在圆上的补集不会导致答案变化。

代码上细节有点多,需要仔细实现。

Code
// Cirno is not baka!
#include <bits/stdc++.h>
#define For(i, a, b) for (int i = (a); i <= (int)(b); ++i)
#define Rof(i, a, b) for (int i = (a); i >= (int)(b); --i)
#define Debug(...) { \
    fprintf(stderr, "Function{%s},line[%d]:\t", __FUNCTION__, __LINE__); \
    fprintf(stderr, __VA_ARGS__); \
    fprintf(stderr, "\n"); \
}
#define FILE(filename) { \
    freopen(#filename ".in", "r", stdin); \
    freopen(#filename ".out", "w", stdout); \
}
#define All(x) x.begin(), x.end()
#define rAll(x) x.rbegin(), x.rend()
#define pii pair<int, int>
#define fi first
#define se second
#define i64 long long
#define mkp make_pair
// #define int long long
using namespace std;

const int _N = 1e6 + 5, mod = 1e9 + 7;
template<typename T> void Max(T &x, T y) {x = max(x, y);}
template<typename T> void Min(T &x, T y) {x = min(x, y);}
template<typename T1, typename T2>
void Addmod(T1 &x, T2 y) {x += y; x >= mod ? x -= mod : x;}
template<typename T1, typename T2>
T1 Add(T1 x, T2 y) {x += y; return x >= mod ? x - mod : x;}

namespace BakaCirno {
    const double PI = acos(-1), inf = 1e18;
    int N, radiu;
    struct Vec2 {
        int x, y;
        Vec2(int x = 0, int y = 0) : x(x), y(y) {}
        double operator() () {return sqrt(1. * x * x + 1. * y * y);}
    } P[_N];
    vector<pair<double, double>> vec;
    void Solve() {
        cin >> N >> radiu;
        For(i, 1, N) {
            cin >> P[i].x >> P[i].y;
            double mid = atan2(P[i].y, P[i].x);
            double angle = acos(radiu / P[i]());
            double L = mid - angle, R = mid + angle;
            auto Mod = [](double &angle) {
                while (angle < 0) angle += 2 * PI;
                while (angle >= 2 * PI) angle -= 2 * PI;
            };
            Mod(L), Mod(R);
            if (L > R) swap(L, R);
            vec.emplace_back(L, R);
            assert(L >= 0 && L <= 2 * PI);
            assert(R >= 0 && R <= 2 * PI);
        }
        sort(vec.begin(), vec.end());
        auto Calc = [](const vector<double> &A)->int {
            static double f[_N]; int len = 0;
            For (i, 0, A.size() - 1) {
                *upper_bound(f + 1, f + len + 1, A[i]) = A[i];
                if (f[len + 1]) ++len;
            }
            For(i, 0, len) f[i] = inf;
            return len;
        };
        int ans = 0;
        For(i, 0, N - 1) {
            vector<double> cur;
            cur.emplace_back(vec[i].se);
            For(j, i + 1, N - 1)
                if (vec[j].fi <= vec[i].se && vec[j].se >= vec[i].se)
                    cur.push_back(vec[j].se);
            ans = max(ans, Calc(cur));
        }
        cout << ans << '\n';
    }
}

signed main() {
    // FILE(test);
    cin.tie(0)->sync_with_stdio(0); int T = 1;
    // cin >> T;
    while (T--) BakaCirno::Solve();
}