【2021牛客多校10J】Illuminations (凸包切线+环形最小覆盖)

题目链接

题目大意

一个 n n n 边形凸包外有 m m m 盏路灯,要选出尽可能少的路灯,使得凸包的外围都被照亮。( n , m ≤ 2 × 1 0 5 n,m\le 2\times 10^5 n,m2×105

思路

Step 1 凸包切线

要找路灯 P ( x 0 , y 0 ) P(x_0, y_0) P(x0,y0) 的切线,可以先找到凸包横坐标最小的点 Q 1 ( x 1 , y 1 ) Q_1(x_1,y_1) Q1(x1,y1) 和横坐标最大的点 Q 2 ( x 2 , y 2 ) Q_2(x_2,y_2) Q2(x2,y2) ,然后分类讨论。

  1. x 0 < x 1 x_0<x_1 x0<x1 ,直接在 Q 1 , Q 2 Q_1, Q_2 Q1,Q2 分割成的两段上二分
  2. 否则,若 P Q 1 PQ_1 PQ1 与凸包有交点,可二分求出交点附近的顶点 Q 3 Q_3 Q3,并在 Q 1 Q_1 Q1 Q 3 Q_3 Q3 分割成的两段上二分

Step 2 环形最小覆盖

预处理出每个点经过1个区间所能到达的最右点。(注意特判超过右端点的情况)
然后倍增。

代码

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define per(i, r, l) for (int i = r; i >= l; --i)
using namespace std;

const int N = 200005;
const int mod = 998244353;
typedef long long ll;
class point {
   public:
    ll x, y;
    point() {}
    point(ll _x, ll _y) : x(_x), y(_y) {}
    bool operator<(const point &rhs) const {
        return x == rhs.x ? y < rhs.y : x < rhs.x;
    }
    point operator+(const point &rhs) const {
        return point(x + rhs.x, y + rhs.y);
    }
    point operator-(const point &rhs) const {
        return point(x - rhs.x, y - rhs.y);
    }
    void read() { scanf("%lld%lld", &x, &y); }
};
ll dot(point p1, point p2) { return p1.x * p2.x + p1.y * p2.y; }
ll cross(point p1, point p2) { return p1.x * p2.y - p1.y * p2.x; }
int n, m;
point vert[N * 2];
point light[N];
int i1 = 1, i2 = 1;
int cut0[N], cut1[N];

bool to_dir(point p, int i) {
    return cross(vert[i] - p, vert[i + 1] - vert[i]) > 0;
}

int get_cut(point p, int l, int r, bool dir) {
    if (l > n) l -= n;
    if (r < l) r += n;
    while (l < r) {
        int mid = (l + r) / 2;
        if (to_dir(p, mid) == dir) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l > n ? l - n : l;
}
int get_inter(point p) {
    int l = i1, r = i1 + n - 1;
    while (l < r) {
        int mid = (l + r + 1) / 2;
        if (cross(vert[mid] - vert[i1], p - vert[i1]) >= 0) {
            l = mid;
        } else
            r = mid - 1;
    }
    return l > n ? l - n : l;
}

void get_cuts() {
    point q1 = vert[i1], q2 = vert[i2];
    rep(i, 1, m) {
        point p = light[i];
        if (p < q1) {
            cut1[i] = get_cut(light[i], i1, i2, 1);  //+
            cut0[i] = get_cut(light[i], i2, i1, 0);  //-
        } else {
            int i3 = get_inter(p);
            // printf("i=%d i3=%d\n", i, i3);
            cut0[i] = get_cut(light[i], i1, i3, 0);      //-
            cut1[i] = get_cut(light[i], i3 + 1, i1, 1);  //+
        }
    }
}
int foot[N];

void init() {
    rep(i, 1, n) { foot[i] = 0; }
    rep(i, 1, m) {
        int l = cut0[i], r = cut1[i];
        if (r > cut1[foot[l]]) foot[l] = i;
    }
    int cur = 0;
    // printf("foot: ");
    rep(i, 1, n) {
        if (cut1[foot[i]] > cut1[cur]) cur = foot[i];
        if (cut1[cur] > i) foot[i] = cur;
        // printf("%d ", foot[i]);
    }
    rep(i, 1, n) {
        if (cut1[cur] - n > i && cut1[cur] - n > cut1[foot[i]]) foot[i] = cur;
    }
    // printf("\n");
}
ll dp[N][20];
bool get_dp() {
    rep(i, 1, n) {
        if (!foot[i]) return false;
        dp[i][0] = cut1[foot[i]] - i;
        if (dp[i][0] > n) dp[i][0] -= n;
    }
    rep(j, 1, 19) {
        rep(i, 1, n) {
            ll t = i + dp[i][j - 1];
            t = (t - 1) % n + 1;
            dp[i][j] = dp[i][j - 1] + dp[t][j - 1];
        }
    }
    return true;
}
int cal_step(int i) {
    int ret = 0, len = 0;
    per(j, 19, 0) {
        if (len + dp[i][j] < n) {
            ret += 1 << j;
            len += dp[i][j];
            i += dp[i][j];
            if (i > n) i -= n;
        }
    }
    return ret + 1;
}
int ans[N];
void work() {
    int ret = m + 1, st = 1;
    rep(i, 1, n) {
        int cur = cal_step(i);
        if (cur < ret) {
            ret = cur;
            st = i;
        }
    }
    int x = st;
    rep(i, 1, ret) {
        ans[i] = foot[x];
        x = cut1[foot[x]];
        if (x > n) x -= n;
    }
    printf("%d\n", ret);
    rep(i, 1, ret) { printf("%d ", ans[i]); }
}

int main() {
    scanf("%d%d", &n, &m);
    rep(i, 1, n) {
        vert[i].read();
        vert[i + n] = vert[i];
    }
    vert[0] = vert[n];
    rep(i, 1, m) { light[i].read(); }
    i1 = 1, i2 = 1;
    rep(i, 1, n) {
        if (vert[i] < vert[i1]) i1 = i;
        if (vert[i2] < vert[i]) i2 = i;
    }
    get_cuts();
    rep(i, 1, m) {
        if (cut1[i] < cut0[i]) cut1[i] += n;
        // printf("%d %d\n", cut0[i], cut1[i]);
    }
    init();
    if (!get_dp()) {
        printf("-1\n");
    } else {
        work();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值