LOJ #2307. 「NOI2017」分身术
题目叙述
一个点集,每次去掉一个集合内部的一些点(不超过 100 个),求剩下节点构成的凸包面积是多少。强制在线。
题解
基本做法是每次求出 100 层凸包(一层一层向内求凸包)。每次去掉一些节点,就找出最内部的没有任何一个节点被去掉的凸包,向外每层相当于添加一个凸包的一个连续部分,去掉原先的一部分,这个直接线段树分裂&合并就可以了。
具体来说需要在内层凸包上二分一下外层连续段的部分的两个端点在内层凸包上的切点。把中间的去掉,那个区间的一部分加上。
- 动态凸包
- 凸包让人感觉神秘的是它需要维护的是一个圈,这是我们不希望看到的。因此考虑改成维护两个凸壳。两个凸壳公用两个端点。有人问如果两个凸壳左边右边公用的是线段怎么办。我说如果真的按照 \(y\) 轴从小到大排序,那么是不会有问题的。因为下凸壳左侧如果从小到大排序就不会出现竖直的情况。具体写的时候其实可以只写一个维护上凸壳的东西,然后将所有 \(y\) 坐标正负取反,按照上凸壳的方式将所有点排序(如果重新自己排序的话就需要按照 \(x\) 坐标相同时 \(y\) 坐标从大到小的顺序排序)就可以做到这件事情。
- 用动态开点线段树维护这件事情。如果需要分裂,可以写线段树分裂。线段树每个节点维护区间最左和最右。
- 二分的时候可以看这个节点在这个区间左儿子的最优解点和右儿子的最左节点连线的上方/下方。具体来说可以通过叉积来实现这件事情。
- 如果想回退(回退插入或者合并操作)可以通过记录目前一共有多少个节点来实现,把新加入的节点都删掉就可以了。
- push up 的同时可以维护面积。
总结
- 去掉节点的凸包考虑维护若干层凸包。
代码
不是我的。
#include <bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define eb emplace_back
#define BE(x) (x).begin(), (x).end()
using namespace std;
using ll = long long;
const int N = 100100, T = N * 20;
int n, m, c[N], id[N], inv[N], tag[N];
pair<int, int> q[N];
struct ConvexHull { /////////////////////////////////////////////////////////////////////////
pair<int, int> p[N];
ll cross(int i, int j, int k) {
int x1 = p[j].fi - p[i].fi, y1 = p[j].se - p[i].se;
int x2 = p[k].fi - p[i].fi, y2 = p[k].se - p[i].se;
return 1ll * x1 * y2 - 1ll * y1 * x2;
}
ll ans[T];
int lc[T], rc[T], mn[T], mx[T], tot, cur;
// mn, mx 表示子树对应区间内凸壳上的最左最右两个点
// ans 表示这两个点连线和区间凸壳之间的面积
// 二分斜率时使用 mx[lc], mn[rc] 连线
// 线段树下标是横坐标排名
int st[N], tp, col[N], rt[110];
int alloc(int x = 0) {
int p = ++tot;
lc[p] = rc[p] = 0, mn[p] = mx[p] = x, ans[p] = 0;
return p;
}
int clone(int x) {
int p = ++tot;
lc[p] = lc[x], rc[p] = rc[x];
ans[p] = ans[x], mn[p] = mn[x], mx[p] = mx[x];
return p;
}
void up(int x) {
if (!lc[x] || !rc[x]) {
int ch = lc[x] | rc[x];
ans[x] = ans[ch], mn[x] = mn[ch], mx[x] = mx[ch];
return;
}
mn[x] = mn[lc[x]], mx[x] = mx[rc[x]];
ans[x] = ans[lc[x]] + ans[rc[x]] +
cross(mn[x], mx[x], mn[rc[x]]) + cross(mn[x], mn[rc[x]], mx[lc[x]]);
}
int build(int x, int l, int r) {
int p = alloc(x);
if (l < r) {
int m = (l + r) >> 1;
if (x <= m)
lc[p] = build(x, l, m);
else
rc[p] = build(x, m + 1, r);
}
return p;
}
int merge(int x, int y, int l, int r) {
if (!x || !y) return x | y;
x = clone(x);
int m = (l + r) >> 1;
lc[x] = merge(lc[x], lc[y], l, m);
rc[x] = merge(rc[x], rc[y], m + 1, r);
return up(x), x;
}
int split(int ql, int qr, int l, int r, int p) {
if (!p || ql > mx[p] || qr < mn[p]) return 0;
if (ql <= l && qr >= r) return p;
int m = (l + r) >> 1;
int x = split(ql, qr, l, m, lc[p]);
int y = split(ql, qr, m + 1, r, rc[p]);
if (!x && !y) return 0;
int res = ++tot;
lc[res] = x, rc[res] = y, up(res);
return res;
}
int qryr(int k, int l, int r, int p) { // 二分点k到凸壳p的右切点
if (!p || k > mx[p]) return n + 1;
if (l == r) return l;
int m = (l + r) >> 1;
if (!lc[p] || k > mx[lc[p]] || (rc[p] && cross(k, mx[lc[p]], mn[rc[p]]) > 0))
return qryr(k, m + 1, r, rc[p]);
else
return qryr(k, l, m, lc[p]);
}
int qryl(int k, int l, int r, int p) {// 二分点k到凸壳p的左切点
if (!p || k < mn[p]) return 0;
if (l == r) return l;
int m = (l + r) >> 1;
if (!rc[p] || k < mn[rc[p]] || (lc[p] && cross(k, mx[lc[p]], mn[rc[p]]) > 0))
return qryl(k, l, m, lc[p]);
else
return qryl(k, m + 1, r, rc[p]);
}
void init(int mxdep) { // 剥洋葱
for (int d = 1; d <= mxdep; d++) {
tp = 0;
for (int i = 1; i <= n; i++) if (!col[i]) {
while (tp > 1 && cross(st[tp - 1], st[tp], i) > 0) tp--;
st[++tp] = i;
}
if (!tp) break;
for (int i = 1; i <= tp; i++) {
col[st[i]] = d;
int t = build(st[i], 1, n);
rt[d] = merge(rt[d], t, 1, n);
}
}
cur = tot; // 删除临时版本用
}
vector<int> wow[110];
void ins(int &t, int s, int l, int r) { // 将凸壳s的[l,r]连续段并到凸壳t上
s = split(l, r, 1, n, s);
if (!s || !t) { t |= s; return; }
l = qryl(mn[s], 1, n, t), r = qryr(mx[s], 1, n, t);
l = split(1, l, 1, n, t), r = split(r, n, 1, n, t);
t = merge(l, s, 1, n), t = merge(t, r, 1, n);
}
ll hull(const vector<int> &c) { // 求答案
for (int x: c) wow[col[x]].eb(x);
int i = 0;
while (!wow[i + 1].empty()) i++;
int t = rt[i + 1];
while (i) {
int last = 0;
sort(BE(wow[i]));
for (int x: wow[i]) {
if (x > last + 1)
ins(t, rt[i], last + 1, x - 1);
last = x;
}
if (last < n)
ins(t, rt[i], last + 1, n);
i--;
}
for (int x: c) wow[col[x]] = vector<int>();
tot = cur; // 删除临时版本
return ans[t];
}
} h1, h2; ///////////////////////////////////////////////////////////////////////////////////
// h1 为上凸壳,h2为下凸壳。
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 1, x, y; i <= n; i++) {
cin >> x >> y;
id[i] = i;
h1.p[i] = mp(x, y);
h2.p[i] = mp(x, -y);
}
sort(id + 1, id + n + 1, [](int x, int y) { return h1.p[x] < h1.p[y]; });
for (int i = 1; i <= n; i++) inv[id[i]] = i, q[i] = h1.p[id[i]];
swap(h1.p, q);
for (int i = 1; i <= n; i++) inv[id[i]] = i, q[i] = h2.p[id[i]];
swap(h2.p, q);
h1.init(101), h2.init(101);
ll lastans = n - 1;
for (int k; m; m--) {
cin >> k;
vector<int> vec;
for (int i = 1; i <= k; i++) {
cin >> c[i];
c[i] = (c[i] + lastans) % n + 1;
vec.eb(inv[c[i]]);
}
cout << (lastans = h1.hull(vec) + h2.hull(vec)) << "\n";
}
}