回滚莫队
简介:远看是莫队(\(r\)),近看是暴力(\(l\),以及左右端点在同一块)。
还记得普通莫队里面怎么说的吗?
注意两个操作有时候会西掉一个,有时候还要在数据结构上操作,但这不在这篇文章的范围内。
所以,这篇文章就会讲述如何应对“两个操作西掉一个”的情况。
删除西掉了(更加常见)
和正常莫队的排序方法一样。
注意!由于回滚莫队的特殊性(不支持增加或删除),所以你不能在回滚莫队上使用奇偶排序。
对于每一个询问:
如果 \(l\) 换块了,将 \(l\) 初始化为这一个块的右端点 \(+ 1\),\(r\) 初始化为这一个块的右端点。
然后如下回答:
-
如果这个询问只涉及到一个块,直接暴力。
-
否则,将 \(r\) 滑动至这个询问的右端点,\(l\) 滑动至这个询问的左端点,记录答案后撤销 \(l\) 的更改,使得莫队回滚到正确的位置以应对下一个询问。
比如说区间最小值(我知道这可以用线段树,但是莫队可以解决一些没有结合律的问题),你无法在删除后得知新的最小值。这就是经典的删除西掉的情况。
增加西掉了
跟删除西掉的情况差不多:
对于每一个询问:
如果 \(l\) 换块了,将 \(l\) 初始化为这一个块的左端点,\(r\) 初始化为 \(N\)。
然后如下回答:
-
如果这个询问只涉及到一个块,直接暴力。
-
否则,将 \(r\) 滑动至这个询问的右端点,\(l\) 滑动至这个询问的左端点,记录答案后撤销 \(l\) 的更改,使得莫队回滚到正确的位置以应对下一个询问。
比如说区间 mex(其实是另一种形式的最小值),你在增加一个元素后无法确定新的 mex。这种情况只能删除。
区间众数
这里介绍两种一般方法。
回滚莫队
这就很好想了,我们将所有数的出现次数取最大值,可知这是一个最大值问题,回滚莫队秒了。
优点是思维很好搞,缺点是不能在线,在强制在线的题目里用不了。
#include <bits/stdc++.h>
using namespace std;
const int kB = 450;
struct node {
int l, r, id;
}qry[400040];
int n, q, arr[100010], cnt[200020], ans[400040], bel[400040];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; i++) {
cin >> arr[i];
bel[i] = i / kB + 1;
}
for(int i = 1; i <= q; i++) {
cin >> qry[i].l >> qry[i].r;
qry[i].id = i;
}
sort(qry + 1, qry + q + 1, [](node a, node b) {return bel[a.l] == bel[b.l]? a.r < b.r : bel[a.l] < bel[b.l];});
int l = 1, r = 0, s = 0;
for(int i = 1; i <= q; i++) {
if(bel[qry[i].l] != bel[qry[i - 1].l]) {
for(int i = l; i <= r; i++) {
cnt[arr[i] + 100010]--;
}
l = bel[qry[i].l] * kB, r = l - 1, s = 0;
}
if(bel[qry[i].l] == bel[qry[i].r]) {
int tmp = 0;
for(int j = qry[i].l; j <= qry[i].r; j++) {
cnt[arr[j] + 100010]++;
tmp = max(tmp, cnt[arr[j] + 100010]);
}
for(int j = qry[i].l; j <= qry[i].r; j++) {
cnt[arr[j] + 100010]--;
}
ans[qry[i].id] = tmp;
}else {
int tmp = 0;
for(; r < qry[i].r; ) {
r++;
cnt[arr[r] + 100010]++;
s = max(s, cnt[arr[r] + 100010]);
}
for(int j = qry[i].l; j < l; j++) {
cnt[arr[j] + 100010]++;
tmp = max(tmp, cnt[arr[j] + 100010]);
}
for(int j = qry[i].l; j < l; j++) {
cnt[arr[j] + 100010]--;
}
ans[qry[i].id] = max(s, tmp);
}
}
for(int i = 1; i <= q; i++) {
cout << ans[i] << '\n';
}
return 0;
}
分块
分块。记录块内区间的众数(\(\sqrt{n}^2 = n\),不会爆炸),块内直接记录答案,块外暴力更新答案。
优点是在线,缺点是不如莫队好搞。