题目大意
一个 n n n 边形凸包外有 m m m 盏路灯,要选出尽可能少的路灯,使得凸包的外围都被照亮。( n , m ≤ 2 × 1 0 5 n,m\le 2\times 10^5 n,m≤2×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) ,然后分类讨论。
- x 0 < x 1 x_0<x_1 x0<x1 ,直接在 Q 1 , Q 2 Q_1, Q_2 Q1,Q2 分割成的两段上二分
- 否则,若 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;
}