带修改莫队

请确保您已经会普通莫队算法了.如果您还不会,请先阅读前面的 普通莫队算法.

特点

普通莫队是不能带修改的.

我们可以强行让它可以修改,就像 DP 一样,可以强行加上一维 时间维, 表示这次操作的时间.

时间维表示经历的修改次数.

即把询问 [l,r][l,r] 变成 [l,r,time][l,r,\text{time}].

那么我们的坐标也可以在时间维上移动,即 [l,r,time][l,r,\text{time}] 多了一维可以移动的方向,可以变成:

  • [l−1,r,time][l-1,r,\text{time}]
  • [l+1,r,time][l+1,r,\text{time}]
  • [l,r−1,time][l,r-1,\text{time}]
  • [l,r+1,time][l,r+1,\text{time}]
  • [l,r,time−1][l,r,\text{time}-1]
  • [l,r,time+1][l,r,\text{time}+1]

这样的转移也是 O(1)O(1) 的,但是我们排序又多了一个关键字,再搞搞就行了.

可以用和普通莫队类似的方法排序转移,做到 O(n5/3)O(n^{5/3}).

这一次我们排序的方式是以 n2/3n^{2/3} 为一块,分成了 n1/3n^{1/3} 块,第一关键字是左端点所在块,第二关键字是右端点所在块,第三关键字是时间.

最优块长以及时间复杂度分析

我们设序列长为 nn,mm 个询问,tt 个修改.

带修莫队排序的第二关键字是右端点所在块编号,不同于普通莫队.

想一想,如果不把右端点分块:

  • 乱序的右端点对于每个询问会移动 nn 次.
  • 有序的右端点会带来乱序的时间,每次询问会移动 tt 次.

无论哪一种情况,带来的时间开销都无法接受.

接下来分析时间复杂度.

设块长为 ss,则有 ns\dfrac{n}{s} 个块.对于块 ii 和块 jj,记有 qi,jq_{i,j} 个询问的左端点位于块 ii,右端点位于块 jj.

每「组」左右端点不换块的询问 (i,j)(i,j),端点每次移动 O(s)O(s) 次,时间单调递增,O(t)O(t).

左右端点换块的时间忽略不计.

表示一下就是:

∑i=1n/s∑j=i+1n/s(qi,j⋅s+t)=ms+(ns)2t=ms+n2ts2\begin{aligned} &\sum_{i=1}^{n/s}\sum_{j=i+1}^{n/s}(q_{i,j}\cdot s+t)\\ =&ms+\left(\dfrac{n}{s}\right)^2t\\ =&ms+\dfrac{n^2t}{s^2} \end{aligned}

考虑求导求此式极小值.设 f(s)=ms+n2ts2f(s)=ms+\dfrac{n^2t}{s^2}.那 f′(s)=m−2n2ts3=0f'(s)=m-\dfrac{2n^2t}{s^3}=0.

得 s=2n2tm3=21/3n2/3t1/3m1/3=s0s=\sqrt[3]{\dfrac{2n^2t}{m}}=\dfrac{2^{1/3}n^{2/3}t^{1/3}}{m^{1/3}}=s_0.

也就是当块长取 n2/3t1/3m1/3\dfrac{n^{2/3}t^{1/3}}{m^{1/3}} 时有最优时间复杂度 O(n2/3m2/3t1/3)O\left(n^{2/3}m^{2/3}t^{1/3}\right).

常说的 O(n5/3)O\left(n^{5/3}\right) 便是把 n,m,tn,m,t 当做同数量级的时间复杂度.

实际操作中还是推荐设定 n2/3n^{2/3} 为块长.

例题

例题 「国家集训队」数颜色/维护队列

题目大意:给你一个序列,M 个操作,有两种操作:

  1. 修改序列上某一位的数字
  2. 询问区间 [l,r][l,r] 中数字的种类数(多个相同的数字只算一个)

我们不难发现,如果不带操作 1(修改)的话,我们就能轻松用普通莫队解决.

但是题目还带单点修改,所以用 带修改的莫队.

过程

先考虑普通莫队的做法:

  • 每次扩大区间时,每加入一个数字,则统计它已经出现的次数,如果加入前这种数字出现次数为 00,则说明这是一种新的数字,答案 +1+1.然后这种数字的出现次数 +1+1.
  • 每次减小区间时,每删除一个数字,则统计它删除后的出现次数,如果删除后这种数字出现次数为 00,则说明这种数字已经从当前的区间内删光了,也就是当前区间减少了一种颜色,答案 −1-1.然后这种数字的出现次数 −1-1.

现在再来考虑修改:

  • 单点修改,把某一位的数字修改掉.假如我们是从一个经历修改次数为 ii 的询问转移到一个经历修改次数为 jj 的询问上,且 i<ji<j 的话,我们就需要把第 i+1i+1 个到第 jj 个修改强行加上.
  • 假如 $j [!note]- 参考代码
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;

long long qsize;

struct query {
  long long id, t, l, r;

  bool operator<(query b) const {
    if (l / qsize != b.l / qsize) {
      return l / qsize > b.l / qsize;
    } else if (r / qsize != b.r / qsize) {
      return r / qsize > b.r / qsize;
    } else {
      return t > b.t;
    }
  }
} q[150009];

struct operation {
  long long p, x;
} r[150009];

char op;
long long n, m, x, y, cur, qcnt, rcnt, mp[1500009], a[150009], ans[150009];

void add(long long x) {
  if (!mp[x]) {
    cur += 1;
  }
  mp[x] += 1;
}

void del(long long x) {
  mp[x] -= 1;
  if (!mp[x]) {
    cur -= 1;
  }
}

void process() {
  sort(q + 1, q + qcnt + 1);
  long long L = 1, R = 0, last = 0;
  for (long long i = 1; i <= qcnt; i++) {
    while (R < q[i].r) {
      add(a[++R]);
    }
    while (R > q[i].r) {
      del(a[R--]);
    }
    while (L > q[i].l) {
      add(a[--L]);
    }
    while (L < q[i].l) {
      del(a[L++]);
    }
    while (last < q[i].t) {
      last += 1;
      if (r[last].p >= L && r[last].p <= R) {
        add(r[last].x);
        del(a[r[last].p]);
      }
      swap(a[r[last].p], r[last].x);
    }
    while (last > q[i].t) {
      if (r[last].p >= L && r[last].p <= R) {
        add(r[last].x);
        del(a[r[last].p]);
      }
      swap(a[r[last].p], r[last].x);
      last -= 1;
    }
    ans[q[i].id] = cur;
  }
}

signed main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);
  cin >> n >> m;
  qsize = pow(n, 2.0 / 3.0);
  for (long long i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (long long i = 1; i <= m; i++) {
    cin >> op >> x >> y;
    if (op == 'Q') {
      ++qcnt, q[qcnt] = {qcnt, rcnt, x, y};
    } else if (op == 'R') {
      r[++rcnt] = {x, y};
    }
  }
  process();
  for (long long i = 1; i <= qcnt; i++) {
    cout << ans[i] << '\n';
  }
}

本文内容来自 OI Wiki《带修改莫队》;作者与修订历史见原文。AIOJ 对排版、站内链接和资源路径做了格式适配。

内容依 CC BY-SA 4.0 与 SATA 许可共享;附加条款以原文声明为准。感谢 OI Wiki 作者与贡献者;请按 SATA 约定前往 OI Wiki 仓库 Star 支持。