UOJ #191. 【集训队互测2016】Unknown 题解
Description
有一个元素为向量的序列,下标从 \(1\) 开始,初始时为空,现在你需要支持三个操作:
- 在 \(S\) 的末尾添加一个元素 \((x,y)\)。
- 删除 \(S\) 的末尾元素。
- 询问下标在 \([l,r]\) 区间内的元素中,\((x,y)\times S_i\) 的最大值。
其中 \(\times\) 表示向量的叉积,\((x_1,y_1) \times (x_2,y_2) = x_1y_2-x_2y_1\)。
\(0 \leq tp \leq 7, 1 \leq m \leq 5\times 10^5\)。
任意时刻 \(n \geq 0\),\(n\) 为当前序列长度。
1 操作个数不超过 \(3\times 10^5\),且满足 \(-10^9 \leq x \leq 10^9,1 \leq y \leq 10^9\)。
3 操作个数不超过 \(3\times 10^5\),且满足 \(1 \leq x \leq 10^9,-10^9 \leq y \leq 10^9\)。
Solution
首先如果没有删除操作,就直接用向量集这题的做法对于线段树上的节点维护凸包,当一个节点被插满后再建凸包。
加上删除操作后如果暴力重构显然会超时,考虑怎么让这题的势能变得正确。
注意到对于二进制分组后每层的最后一个插满的节点,如果查询时不考虑这个节点,直接向下递归的话也能保证查询复杂度的正确。
所以对于每次插入操作就建好每层除了最后一个插满的节点的凸包,对于删除操作如果删到一个之前已经构建好凸包的节点就把这个节点的凸包清空,这样就能保证重构的总点数是 \(O(m\log m)\) 了。
时间复杂度:\(O(m\log^2m)\)。
Code
#include <bits/stdc++.h>
// #define int int64_t
using i64 = int64_t;
using pii = std::pair<i64, i64>;
const int kMaxN = 5e5 + 5, kMod = 998244353;
int _tp, n, q;
pii pr[kMaxN];
std::vector<int> vec[(1 << 19) + 5], v[(1 << 19) + 5];
bool vis[(1 << 19) + 5];
/*
v[x][0/1] : 线段树 x 号节点的上/下凸包
*/
int fix(i64 x) { return (x % kMod + kMod) % kMod; }
pii sub(pii a, pii b) { return {a.first - b.first, a.second - b.second}; }
i64 mul(pii a, pii b) { return a.first * b.second - a.second * b.first; }
i64 func(pii a, pii b) { return a.first * b.second + a.second * b.first; }
int getid(int dep, int x) { return (1 << (18 - dep)) + x; }
void build(int x) {
static std::vector<int> tvec;
vis[x] = 1;
tvec = vec[x];
std::sort(tvec.begin(), tvec.end(), [&] (int i, int j) { return pr[i] < pr[j]; });
v[x].clear(), v[x].shrink_to_fit();
for (auto i : tvec) {
for (; v[x].size() >= 2 && mul(sub(pr[i], pr[v[x].back()]), sub(pr[v[x].back()], pr[v[x][v[x].size() - 2]])) <= 0; v[x].pop_back()) {}
v[x].emplace_back(i);
}
}
void clear(int x) {
vis[x] = 0, v[x].clear();
}
i64 query(int x, pii p) {
int L = 0, R = v[x].size(), res = 0;
while (L + 1 < R) {
int mid = (L + R) >> 1;
if (func(p, pr[v[x][mid]]) >= func(p, pr[v[x][mid - 1]])) L = res = mid;
else R = mid;
}
return func(p, pr[v[x][res]]);
}
i64 query(int x, int l, int r, int ql, int qr, pii p) {
if (l > qr || r < ql) return -1e18;
else if (l >= ql && r <= qr) {
if (l == r) return func(p, pr[l]);
else if (vis[x]) return query(x, p);
}
int mid = (l + r) >> 1;
return std::max(query(x << 1, l, mid, ql, qr, p), query(x << 1 | 1, mid + 1, r, ql, qr, p));
}
void dickdreamer() {
int ans = 0;
n = 0;
for (int cs = 1; cs <= q; ++cs) {
int op;
pii p;
std::cin >> op;
if (op == 1) {
std::cin >> p.first >> p.second;
p.first *= -1;
pr[n] = p;
for (int i = 0; i <= 18; ++i) {
int t = (n >> i);
vec[getid(i, t)].emplace_back(n);
if ((n % (1 << i)) == (1 << i) - 1 && t && !vis[getid(i, t - 1)]) {
build(getid(i, t - 1));
}
}
++n;
} else if (op == 2) {
--n;
for (int i = 0; i <= 18; ++i) {
int t = (n >> i);
vec[getid(i, t)].pop_back();
if ((n % (1 << i)) == (1 << i) - 1 && vis[getid(i, t)]) {
clear(getid(i, t));
}
}
} else {
int l, r;
std::cin >> l >> r >> p.first >> p.second;
ans ^= fix(query(1, 0, (1 << 18) - 1, l - 1, r - 1, p));
}
}
std::cout << ans << '\n';
for (int i = 0; i < (1 << 19); ++i) {
vec[i].clear(), vec[i].shrink_to_fit();
v[i].clear(), v[i].shrink_to_fit();
vis[i] = 0;
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
std::cin >> _tp;
while (std::cin >> q && q) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}