a[x].remove(y);
// 重新计算 b[x / B]int ib = x / B;
int l = ib * B, r = min(N, l + B);
b[ib] = -1;
for (int i = l; i < r; i++)
if (!a[i].empty())
b[ib] = max(b[ib], *a[i].rbegin());
时间:。
完整代码
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<string> type(n);
vector<int> x(n), y(n);
for (int i = 0; i < n; i++)
cin >> type[i] >> x[i] >> y[i];
vector<int> real_x = compress(x);
int N = (int) real_x.size();
int B = (int) sqrt(N);
int NB = (N + B - 1) / B;
vector<set<int>> a(N);
vector<int> b(NB, -1);
auto query = [&](int l, int r, int y) -> int {
int lb = (l + B - 1) / B;
int rb = r / B;
if (lb > rb) {
for (int i = l; i < r; i++)
if (!a[i].empty() && *a[i].rbegin() > y)
return i;
return r;
}
for (int i = l; i < lb * B; i++)
if (!a[i].empty() && *a[i].rbegin() > y)
return i;
for (int ib = lb; ib < rb; ib++)
if (b[ib] > y)
for (int i = ib * B; i < (ib + 1) * B; i++)
if (!a[i].empty() && *a[i].rbegin() > y)
return i;
for (int i = rb * B; i < r; i++)
if (!a[i].empty() && *a[i].rbegin() > y)
return i;
return r;
};
for (int i = 0; i < n; i++) {
if (type[i] == "add") {
a[x[i]].insert(y[i]);
b[x[i] / B] = max(b[x[i] / B], y[i]);
} elseif (type[i] == "remove") {
a[x[i]].erase(y[i]);
int ib = x[i] / B;
int l = ib * B, r = min(l + B, N);
// 重新计算 b[ib]
b[ib] = -1;
for (int j = l; j < r; j++)
if (!a[j].empty())
b[ib] = max(b[ib], *a[j].rbegin());
} else {
int j = query(x[i] + 1, N, y);
if (j == N)
cout << -1 << '\n';
else
cout << real_x[j] << ' ' << *a[j].upper_bound(y[i]) << '\n';
}
}
}
对于每个块 ,除了维护其中的纵坐标的最大值,也可以用一个 multiset<int> c[i] 来维护其中的纵坐标。这样,对于 remove x y,我们只需要
a[x].erase(y);
c[x / B].extract(y); // since C++20. 只删一个 y
时间是 。
不过这样并不比上面的写法更块。
用线段树解决这题
vector<int> b(2 * N, -1);
vector<set<int>> a(N);
线段树的每个节点 b[i] 维护「横坐标在相应范围内的点」的纵坐标的最大值。
每对个横坐标 ,用一个 set<int> a[x] 来存储横坐标等于 的点的纵坐标。
单点修改
对于操作 add x y 或 remove x y,
修改 a[x]
更新线段树的叶子 b[x + N]
从下往上,更新 x + N 的祖先节点
区间查询
find x, y:找出区间 中第一个满足 b[i + N] > y 的 i。
从下往上,把所查询的区间 拆解为极大整块,找出从左到右第一个满足 b[k] > y 的极大整块 。
在每一层
如果左边的极大整块 满足 b[l] > y,那么 就是 ,结束。
如果右边的极大整块 满足 b[r - 1] > y,那么 。
从节点 开始,往下查找,定位到满足 b[p] > y 的叶子 。
在 a[p - N] 中查找第一个大于 的纵坐标。
我们把 1 2 两步称为在线段树上二分查找。
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<string> type(n);
vector<int> x(n), y(n);
for (int i = 0; i < n; i++)
cin >> type[i] >> x[i] >> y[i];
vector<int> real_x = compress(x); //坐标压缩int N = (int) real_x.size();
vector<set<int>> a(N);
vector<int> b(2 * N, -1); //线段树for (int i = 0; i < n; i++) {
if (type[i] == "add") {
a[x[i]].insert(y[i]);
int p = x[i] + N;
b[p] = *a[x[i]].rbegin();
for (p /= 2; p > 0; p /= 2) {
b[p] = max(b[p * 2], b[p * 2 + 1]);
}
} elseif (type[i] == "remove") {
a[x[i]].erase(y[i]);
int p = x[i] + N;
b[p] = a[x[i]].empty() ? -1 : *a[x[i]].rbegin();
for (p /= 2; p > 0; p /= 2) {
b[p] = max(b[p * 2], b[p * 2 + 1]);
}
} else {
int l = x[i] + 1 + N, r = N + N;
int p = -1;
while (l < r) {
if (l & 1) {
if (b[l] > y[i]) { p = l; break; }
l++;
}
if (r & 1) {
if (b[r - 1] > y[i]) p = r - 1;
r--;
}
l /= 2; r /= 2;
}
if (p == -1)
cout << -1 << '\n';
else {
while (p < N) {
if (b[p * 2] > y[i])
p = 2 * p;
else
p = 2 * p + 1;
}
cout << real_x[p - N] << ' ' <<
*a[p - N].upper_bound(y[i]) << '\n';
}
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int maxn = 1 << 20;
vector<vector<int>> f(maxn + 1); // f[i]:能整除 i 的素数幂vector<int> np(maxn + 1); // number of prime factors
vector<int> val;
for (int i = 2; ; i++) {
if (f[i].empty()) {
for (int j = i; j <= maxn; j += i) {
np[j]++;
int t = j;
int d = 1;
do {
d *= i;
t /= i;
f[j].push_back(d);
} while (t % i == 0);
}
}
if (np[i] == 1) {
val.push_back(i);
if (i > (int) 1e6)
break;
}
}
vector<int> order(maxn + 1);
int N = (int) val.size() - 1; // N = 78734for (int i = 0; i < N; i++)
order[val[i]] = i;
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
vector<int> L(q), R(q);
for (int i = 0; i < q; i++) {
cin >> L[i] >> R[i];
L[i]--;
R[i]--;
}
vector<int> I(q);
iota(I.begin(), I.end(), 0);
sort(I.begin(), I.end(), [&](int i, int j) {return R[i] < R[j];});
vector<int> ans(q);
auto it = I.begin();
for (int i = 0; i < n; i++) {
for (int d : f[a[i]]) {
int p = order[d] + N;
pos[p] = i;
for (p >>= 1; p; p >>= 1) {
pos[p] = min(pos[2 * p], pos[2 * p + 1]);
}
}
for ( ;it != I.end() && R[*it] == i; ++it) {
int l = N, r = 2 * N;
int p = r;
while (l < r) {
if (l & 1) {
if (pos[l] < L[*it]) {
p = l;
break;
}
l++;
}
if (r & 1) {
if (pos[r - 1] < L[*it])
p = r - 1;
r--;
}
l >>= 1;
r >>= 1;
}
while (p < N) {
if (pos[p * 2] < L[*it])
p = p * 2;
else
p = p * 2 + 1;
}
ans[*it] = val[p - N] - 1;
}
}
for (int x : ans)
cout << x << '\n';
}
另一个写法
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int maxn = 1 << 20;
vector<vector<int>> f(maxn + 1);
vector<int> np(maxn + 1); // number of prime factors
vector<int> val;
for (int i = 2; ; i++) {
if (np[i] <= 1) { // i 是素数幂bool is_prime = np[i] == 0;
for (int j = i; j <= maxn; j += i) {
np[j] += is_prime;
f[j].push_back((int) val.size());
}
val.push_back(i);
if (i > (int) 1e6)
break;
}
}
int N = (int) val.size() - 1; // N = 78734vector<int> pos(2 * N, -1); // 线段树
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
vector<int> L(q), R(q);
for (int i = 0; i < q; i++) {
cin >> L[i] >> R[i];
L[i]--;
R[i]--;
}
vector<int> I(q);
iota(I.begin(), I.end(), 0);
sort(I.begin(), I.end(), [&](int i, int j) {return R[i] < R[j];});
vector<int> ans(q);
auto it = I.begin();
for (int i = 0; i < n; i++) {
for (int d : f[a[i]]) {
int p = d + N;
pos[p] = i;
for (p >>= 1; p; p >>= 1) {
pos[p] = min(pos[2 * p], pos[2 * p + 1]);
}
}
for ( ;it != I.end() && R[*it] == i; ++it) {
int l = N, r = 2 * N;
int p = r;
while (l < r) {
if (l & 1) {
if (pos[l] < L[*it]) {
p = l;
break;
}
l++;
}
if (r & 1) {
if (pos[r - 1] < L[*it])
p = r - 1;
r--;
}
l >>= 1;
r >>= 1;
}
while (p < N) {
if (pos[p * 2] < L[*it])
p = p * 2;
else
p = p * 2 + 1;
}
ans[*it] = val[p - N] - 1;
}
}
for (int x : ans)
cout << x << '\n';
}