回滚莫队

简介:远看是莫队(\(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\),不会爆炸),块内直接记录答案,块外暴力更新答案。

优点是在线,缺点是不如莫队好搞。

posted @ 2024-04-23 15:50  hhc0001  阅读(3)  评论(0编辑  收藏  举报