一、普通莫队
1. 介绍
莫队是一种用来解决如下问题的算法:
只有查询,没有修改。
可以离线。
已知区间
的答案,可以 转移到 或 。
2. 实现
用两个指针
但是,如果所有的查询区间都在极限拉扯(如第一个查询
所以我们考虑优化:把数列分块,分成
3. 块长
当
当
4. 例题
P2709 小 B 的询问
有一个长为
其中
当即将增加一个数时(此时还没更改
删去时同理,
核心代码(
namespace MoDui {
bool cmp(QUESTION x, QUESTION y) {
return belong[x.l] != belong[y.l] ? belong[x.l] < belong[y.l] : x.r < y.r;
}
void init_block() {
block = sqrt(n);
for (int i = 1; i <= n; i++) belong[i] = (i - 1) / block + 1;
}
void add(int x) {
tmp -= cnt[a[x]] * cnt[a[x]];
cnt[a[x]]++;
tmp += cnt[a[x]] * cnt[a[x]];
}
void del(int x) {
tmp -= cnt[a[x]] * cnt[a[x]];
cnt[a[x]]--;
tmp += cnt[a[x]] * cnt[a[x]];
}
void solve() {
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) {
while (l > q[i].l) add(--l);
while (l < q[i].l) del(l++);
while (r < q[i].r) add(++r);
while (r > q[i].r) del(r--);
ans[q[i].id] = tmp;
}
}
}
using namespace MoDui;二、回滚莫队
1. 介绍
回滚莫队用于解决增加
2. 实现
以与普通莫队同样的方法排序。
当询问左右端点
,在同一个块时,暴力求解。 这里的
不再移动到 ,而是 所在的块的下一个块的左端点。 当两次询问的
在同一个块内时,由于 升序,可以直接移动 ,往后加,在暴力计算区间 的答案。 当
到下一个块时,直接暴力将 的结果全部删除,答案清零。 由于只增不删(或只删不增)并且不能在
内处理删(或增),因此 del(或 add)函数不再处理统计答案。
3. 例题
AT_joisc2014_c 歴史の研究
给定一个长度为
其中
求最大操作增加容易,删除却很难,所以考虑回滚莫队。
由于值域较大,还需离散化。
核心代码:
namespace MoDui {
void add(int x) {
cnt[a[x]]++;
tmp = max(tmp, val[a[x]] * cnt[a[x]]);
}
void del(int x) { cnt[a[x]]--; }
void solve() {
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) {
if (q[i].l >= l) {
tmp = 0;
for (int j = l; j <= r; j++) del(j);
r = belong[q[i].l] * block;
l = r + 1;
}
if (belong[q[i].l] == belong[q[i].r]) {
for (int j = q[i].l; j <= q[i].r; j++) add(j);
ans[q[i].id] = tmp;
for (int j = q[i].l; j <= q[i].r; j++) del(j);
tmp = 0;
continue;
}
while (r < q[i].r) add(++r);
int last = tmp;
for (int j = q[i].l; j < l; j++) add(j);
ans[q[i].id] = tmp;
for (int j = q[i].l; j < l; j++) del(j);
tmp = last;
}
}
}
using namespace MoDui;三、树上莫队
0. 前置知识:欧拉序
如用先序遍历遍历这一棵树:

序列为:1 2 5 6 3 7 4
欧拉序与先序遍历的不同点在于:当一个节点的所有子树已经遍历完时,再次将这个节点加入序列中。
则上图的欧拉序为:1 2 5 5 6 6 2 3 7 7 3 4 4 1
粗体字组成的序列与先序遍历序列一致。
1. 介绍
树上莫队常用于解决树上路径或子树区间相关的问题。
2. 实现
对上上面的这个序列进行分析,假设
当
假设
5 6 6 2 3 7 7 3 4
容易发现序列中没有
当
假设
1 2 5 5 6
发现序列中出现了一次的点全部属于答案,直接统计即可。
3. 例题
SP10107 COT2 - Count on a tree II
有
离散化+树上莫队。
核心代码:
namespace MoDui {
bool cmp(QUESTION x, QUESTION y) {
if (belong[x.l] != belong[y.l]) return belong[x.l] < belong[y.l];
if (belong[x.l] % 2) return x.r < y.r;
return x.r > y.r;
}
void dfs(int u, int fa) {
dfn[++timestamp] = u;
st[u] = timestamp;
f[u][0] = fa;
for (int i = 1; i <= 20; i++) f[u][i] = f[f[u][i - 1]][i - 1];
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (v == fa) continue;
dfs(v, u);
}
dfn[++timestamp] = u;
en[u] = timestamp;
}
int lca(int x, int y) {
if (x == y) return x;
if (st[x] < st[y]) swap(x, y);
for (int i = 20; i >= 0; i--)
if (st[f[x][i]] > st[y]) x = f[x][i];
return f[x][0];
}
void add(int x) {
vis[x]++;
if (vis[x] == 1) {
cnt[a[x]]++;
if (cnt[a[x]] == 1) tmp++;
}
if (vis[x] == 2) {
cnt[a[x]]--;
if (!cnt[a[x]]) tmp--;
}
}
void del(int x) {
if (vis[x] == 1) {
if (cnt[a[x]] == 1) tmp--;
cnt[a[x]]--;
}
if (vis[x] == 2) {
if (!cnt[a[x]]) tmp++;
cnt[a[x]]++;
}
vis[x]--;
}
void get_question() {
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
if (st[u] > st[v]) swap(u, v);
int tmp = lca(u, v);
if (tmp == u) q[i] = {st[u], st[v], i, 0};
else q[i] = {en[u], st[v], i, tmp};
}
}
void solve() {
sort(q + 1, q + m + 1, cmp);
int r = 0, l = 1;
for (int i = 1; i <= m; i++) {
while (l > q[i].l) add(dfn[--l]);
while (l < q[i].l) del(dfn[l++]);
while (r < q[i].r) add(dfn[++r]);
while (r > q[i].r) del(dfn[r--]);
if (q[i].lca) add(q[i].lca);
ans[q[i].id] = tmp;
if (q[i].lca) del(q[i].lca);
}
}
}
using namespace MoDui;四、带修莫队
1. 介绍
用于解决带有单点修改的莫队问题。
2. 实现
将询问
则可以
只需要在写一个函数处理
需要注意的是,最优块长为
3. 例题
P1903 [国家集训队] 数颜色 / 维护队列
给定一个长度为
,表示求第 到第 个数有多少个互不相同的数。 ,表示将第 个数改为 。
核心代码:
namespace MoDui {
bool cmp(QUESTION x, QUESTION y) {
if (belong[x.l] != belong[y.l]) return x.l < y.l;
if (belong[x.r] != belong[y.r]) return x.r < y.r;
return x.t < y.t;
}
void add(int x) {
if (!cnt[x]) tmp++;
cnt[x]++;
}
void del(int x) {
cnt[x]--;
if (!cnt[x]) tmp--;
}
void update(int t, int x) {
if (q[x].l <= upd[t].p and upd[t].p <= q[x].r) {
add(upd[t].x);
del(a[upd[t].p]);
}
swap(upd[t].x, a[upd[t].p]);
}
void solve() {
sort(q + 1, q + qcnt + 1, cmp);
int l = 1, r = 0, t = 0;
for (int i = 1; i <= qcnt; i++) {
while (l > q[i].l) add(a[--l]);
while (l < q[i].l) del(a[l++]);
while (r < q[i].r) add(a[++r]);
while (r > q[i].r) del(a[r--]);
while (t < q[i].t) update(++t, i);
while (t > q[i].t) update(t--, i);
ans[q[i].id] = tmp;
}
}
}
using namespace MoDui;