学习笔记:莫队

离线算法。

算法核心思想是优化暴力。

一般形式是若干区间询问。

若能用较少的时间((O(1) - O(log n)))从 ([l, r]) 的答案推演至 ([l, r + 1], [l - 1, r]) 的答案,那么这个问题就可以用莫队优化。

普通莫队

设扩展一次的时间为 (O(a))

([l, r]) 的答案快速推演至 ([l, r + 1], [l, r - 1], [l + 1, r], [l - 1, r])

我们考虑把询问排序,对于相邻的询问,([l_i, r_i]) 移动到 ([l_{i+1}, r_{r+1}]),复杂度是 (O(a(|r_i - r_{i-1}| + |l_{i+1}-l_i|)))

排序方式?考虑分块,把序列分块,设 (i) 所属块为 (pos_i),设块长度为 (S) ,询问排序关键字,先按照左端点块数 (pos_l) 排序,若左端点所属块相等,右端点按从小到大排序。

复杂度:

  • 右指针:(frac{n^2}{S})
  • 左指针:
    • 块内:每次移动最多不会超过 (S) 次,总共 (mS)
    • 块间:即这一块最后询问到下一块第一个询问,下一个询问左端点一定是大于这个询问左端点的。所以总复杂度不超过 (n)

平衡算法 (frac{n^2}{S} = mS)(S = frac{n}{sqrt{m}}) 最优。总复杂度 (O(n sqrt{m} imes a))

模板题:

带修莫队

其实就是多了一个时间维。

([l, r, t]) 代表区间为 ([l, r]),第 (t) 次修改之后的答案。

排序:第一关键字为 (pos_l),第二关键字为 (pos_r),第三关键字为 (t)

(pos_l , pos_r) 相同的一段是一个块。

设每一块长度为 (S),一共有 (frac{n^2}{S}) 块。

  • (l) 的移动不超过 (mS + n)

  • (r) 的移动不超过 (mS + frac{n^2}{S})

  • (t)(frac{n^2}{S^2}t)

(n, m) 同阶时,(S = sqrt[3]{nt}) 取到最优解,复杂度 (O(sqrt[3]{n^4t}))

模板题:

回滚莫队

若只能插入,不能删除,可以用回滚莫队。

排序和普通莫队一样

(R_i)(i) 这个块最右边点的编号。

  • 暴力求块内的所有区间
  • 同一块左端点,他们所有的跨块询问,这么做:首先强行变成区间 ([R_{pos_l}, R_{pos_l}]) 剩下的询问必然在 (r > R_{pos_l})。转移一个询问让右端点跟着走,然后左端点暴力扩展,暴力撤销。

复杂度类似普通莫队,左端点每次暴力扩展暴力撤销都在同一个块里,所以复杂度还是跟普通莫队相同的。

模板题:

树上莫队

一般形式是对树上一条简单路径的某些询问。

考虑把树转化成链,跑欧拉序:

  • dfs 一遍,进入的时候把该节点 push 进序列,退出时再 push 一次。

(L_x, R_x) 分别为进入 / 退出 (x) 点对应的序列位置。

这样的序列,我们发现对于一条 (u, v)(设 (L_u < L_v)) 的路上简单路径上的所有点编号:

  • 如果 (lca(u, v) = u),这些编号恰好是 ([L_u, L_v]) 内出现次数为一的编号。
  • 否则, 这些编号恰好是 ([R_u, L_v]) 内出现次数为一的编号 + 一个 (lca(u, v))

证明可以画画图,感性理解:

  • 如果 (lca(u, v) = u),那么 (u)(v) 的祖先,所以从 (u) 一路 dfs 到 (v) 的第一次,这之中可能会经过其他子树,但肯定会回溯,所以经过其他子树的就肯定出现的两次。
  • 如果 (lca(u, v) ot= u),那么他们不是祖先关系,那么跑 dfs 时,会先进到他们的 LCA,然后到 u,回溯会 LCA,再跑到 v,其中经过别的子树也都出现两次被搞掉了。

统计是否出现两次一般都是开另外一个数组,看这个编号出现过没有,如果出现过你就加上,已经在了你就删掉。

模板:

二次离线莫队

如果对于 ([l, r]) 快速扩展一步到 ([l, r + 1], [l, r - 1], [l + 1, r], [l - 1, r]) 的操作,不好做,可以把所有扩展的询问离线下来,都查一遍,然后重做,每次用之前弄好的询问,复杂度可以降。

模板:

2535. 二次离线莫队

每次扩展的询问形式是 ([l, r]) 中有多少数可以和 (r+1,r,l,l-1) 匹配。

这个东西差分成 (sum_{[1, r]} - sum_{[1, l - 1]}) 这样可以扫描线扫一遍。

至于扫描线,暴力 (n imes C_{14}^7) 是可以过的。

复杂度 (O(n sqrt{n} + n imes C_{14}^7))

如果不离线 (sqrt{n})(C_{14}^7) 会叠加起来。

然后空间就炸了。。

考虑其一,剩下是对称的,考虑 ([l, r]) 跑到 ([l, R]),其中 (r < R)

那么差分的左侧都是 ([1, l - 1]) 中所有跟 ([r, R + 1]) 里对称的个数,这样打包到一个结构体了。

差分的右侧时 ([1, x]) 里和 (x + 1) 匹配的形式,这个东西区间和匹配的数相关,预处理一下就好了。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
const int N = 100005, L = 14;

typedef long long LL;

using namespace std;

int n, m, K, t, a[N], cnt[1 << L], d[1 << L], f[N], tot, pos[N];
LL ans[N], res[N];

// R[i] 表示 [1, i] 有多少和 i + 1 匹配的。

int inline get(int x) {
	int s = 0;
	while (x) x -= x & -x, s++;
	return s;
}

struct Q{
	int l, r, id;
	bool operator < (const Q &b) const {
		if (pos[l] != pos[b.l]) return l < b.l;
		return (pos[l] & 1) ? r < b.r : r > b.r;
	}
} q[N];

struct E{
	int l, r, opt, id;
};

vector<E> e[N];


int main() {
	scanf("%d%d%d", &n, &m, &K); t = sqrt(n);
	for (int i = 1; i <= n; i++) scanf("%d", a + i), pos[i] = (i - 1) / t + 1;
	for (int i = 0; i < (1 << L); i++)
		if (get(i) == K) d[++tot] = i;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= tot; j++) cnt[a[i] ^ d[j]]++;
        f[i] = cnt[a[i + 1]];
	}
	for (int i = 0; i < (1 << L); i++) cnt[i] = 0;
	for (int i = 1; i <= m; i++)
		scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
	sort(q + 1, q + 1 + m);
	int l = 1, r = 0;
	for (int i = 1; i <= m; i++) {
		if (l < q[i].l) e[r].push_back((E) { l, q[i].l - 1, -1, i });
		while (l < q[i].l) res[i] += f[l - 1] + !K, l++;
		if (l > q[i].l) e[r].push_back((E) { q[i].l, l - 1, 1, i });
		while (l > q[i].l) res[i] -= f[l - 2] + !K, l--;
		if (r < q[i].r) e[l - 1].push_back((E) { r + 1, q[i].r, -1, i });
		while (r < q[i].r) res[i] += f[r++];
		if (r > q[i].r) e[l - 1].push_back((E) { q[i].r + 1, r, 1, i });
		while (r > q[i].r) res[i] -= f[--r];
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= tot; j++) cnt[a[i] ^ d[j]]++;
		for (int j = 0; j < e[i].size(); j++) {
			E u = e[i][j];
			for (int k = u.l; k <= u.r; k++)
				res[u.id] += cnt[a[k]] * u.opt;
		}
	}
	for (int i = 1; i <= m; i++) res[i] += res[i - 1], ans[q[i].id] = res[i];
	for (int i = 1; i <= m; i++) printf("%lld
", ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/dmoransky/p/13732724.html