回滚莫队

前情提要

简介:远看是莫队(\(r\)),近看是暴力(\(l\),以及左右端点在同一块)。

还记得普通莫队里面怎么说的吗?

注意两个操作有时候会西掉一个,有时候还要在非线性结构上操作,有的甚至需要在数据结构上再次离线,但这不在这篇文章的范围内。

所以,这篇文章就会讲述如何应对“两个操作西掉一个”的情况。

删除西掉了(更加常见)

和正常莫队的排序方法一样。

注意!由于回滚莫队的特殊性(不支持增加或删除),所以你不能在回滚莫队上使用奇偶排序。

对于每一个询问:

如果 \(l\) 换块了,将 \(l\) 初始化为这一个块的右端点 \(+ 1\)\(r\) 初始化为这一个块的右端点。

然后如下回答:

  • 如果这个询问只涉及到一个块,直接暴力。

  • 否则,将 \(r\) 滑动至这个询问的右端点,\(l\) 滑动至这个询问的左端点,记录答案后撤销 \(l\) 的更改,使得莫队回滚到正确的位置以应对下一个询问。

比如说区间最小值(我知道这可以用线段树,但是莫队可以解决一些没有结合律的问题),你无法在删除后得知新的最小值。这就是经典的删除西掉的情况。

JOISC2014 历史研究 JOISC 怎么这么水

同上:

暴力显然。

\(r\) 滑动时,立即计算贡献,可以保证正确性。

\(l\) 回滚时,仅需回滚 \(cnt\) 即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kB = 450;

struct node {
  int l, r, id;
}qqq[100010];

int n, q, arr[100010], tmp[100010], brr[100010], bel[100010], cnt[100010], tcnt[100010], sum, ans[100010];

signed main() {
  // 1. Preprocess(input, sorting)
  // NO PARITY SORT
  cin >> n >> q;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
    bel[i] = (i - 1) / kB + 1;
    tmp[i] = arr[i];
  }
  sort(tmp + 1, tmp + n + 1);
  for(int i = 1; i <= n; i++) {
    brr[i] = lower_bound(tmp + 1, tmp + n + 1, arr[i]) - tmp;
  }
  for(int i = 1; i <= q; i++) {
    cin >> qqq[i].l >> qqq[i].r;
    qqq[i].id = i;
  }
  sort(qqq + 1, qqq + q + 1, [](node a, node b) {return bel[a.l] == bel[b.l]? a.r < b.r : bel[a.l] < bel[b.l];});
  for(int i = 1, r = 0, p = 0; i <= q; i++) {
    // 2. Block Switch
    if(bel[qqq[i].l] > bel[qqq[i - 1].l]) {
      memset(cnt, 0, sizeof(cnt)); // Limited switch makes brute force avaliable.
      int tmp = bel[qqq[i].l];
      r = tmp * kB;
      p = r + 1; // p is the "pivot" which l starts
      sum = 0;
    }
    // 3. Sigle-block Process
    //Limited length, use brute force
    if(bel[qqq[i].l] == bel[qqq[i].r]) {
      int tmp = 0;
      for(int j = qqq[i].l; j <= qqq[i].r; j++) {
        tcnt[brr[j]]++;
      }
      for(int j = qqq[i].l; j <= qqq[i].r; j++) {
        tmp = max(tmp, arr[j] * tcnt[brr[j]]);
      }
      for(int j = qqq[i].l; j <= qqq[i].r; j++) {
        tcnt[brr[j]]--;
      }
      ans[qqq[i].id] = tmp; 
      continue;
    }
    // 4. Move r
    // r always move to right, so no deletions
    for(; r < qqq[i].r; ) {
      r++;
      cnt[brr[r]]++;
      sum = max(sum, cnt[brr[r]] * arr[r]); // recalculatable
    }
    // 5. Move l from p
    int real = sum;
    for(int l = p; l > qqq[i].l; ) {
      l--;
      cnt[brr[l]]++;
      real = max(real, cnt[brr[l]] * arr[l]); // recalculatable
    }
    // 6. Record, Rollback
    ans[qqq[i].id] = real;
    for(int l = p; l > qqq[i].l; ) {
      l--;
      cnt[brr[l]]--;
    }
  }
  for(int i = 1; i <= q; i++) {
    cout << ans[i] << '\n';
  }
  return 0;
}

增加西掉了

跟删除西掉的情况差不多:

对于每一个询问:

如果 \(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  阅读(39)  评论(0)    收藏  举报