其他分享
首页 > 其他分享> > LOJ #2307. 「NOI2017」分身术

LOJ #2307. 「NOI2017」分身术

作者:互联网

题目叙述

一个点集,每次去掉一个集合内部的一些点(不超过 100 个),求剩下节点构成的凸包面积是多少。强制在线。

题解

基本做法是每次求出 100 层凸包(一层一层向内求凸包)。每次去掉一些节点,就找出最内部的没有任何一个节点被去掉的凸包,向外每层相当于添加一个凸包的一个连续部分,去掉原先的一部分,这个直接线段树分裂&合并就可以了。

具体来说需要在内层凸包上二分一下外层连续段的部分的两个端点在内层凸包上的切点。把中间的去掉,那个区间的一部分加上。

总结

代码

不是我的。

#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";
	}
}

标签:return,lc,分身术,LOJ,mn,int,NOI2017,rc,mx
来源: https://www.cnblogs.com/YouthRhythms/p/16541459.html