前置芝士
分块的思想。
暴力
字母/变量的含义
(n):序列的长度。
(q):询问个数。
(d):块的大小。
(num[i]):(i)这个元素所在的块的编号。
(q[i]):一个询问。
(q[i].x):一个询问的左端点。
(q[i].y):一个询问的右端点。
(q[i].id):一个询问的编号。
普通莫队
考虑这样一个问题:
一个长为 (n) 的序列,(q) 次询问,每次询问 ([l,r]) 这个区间的元素的和。
(0 < n,q leq 100000)
考虑暴力,每次扫描 ([l,r]) 这个区间的到答案复杂度 (O(n ^2))。
考虑优化,假如已知 ([x,y]) 这个区间的答案,下次询问 ([x - 2, y + 2]) 的答案,可以从上一个答案转移出来。
但这样有可能会被卡比如 ([1,1]) 与 ([n,n]) 交替问你。
再考虑优化,对询问离线,按照一种规则去排序,使得左右指针移动次数尽可能少。
排序规则为:先对序列分块,左端点在同一块内的按右端点升序,否则按块的序号升序。
普通莫队的要求:
- 已知 ([l,r]) 的答案要能够 (O(1)) 的转移出 ([l - 1, r], [l, r - 1], [l + 1, r], [l, r + 1]) 的答案。
- 可以离线。
- 无修改。
这样的复杂度:
- 同一块内的询问右端点最多总共移动 (n) 次,有 (largefrac{n}{d}) 复杂度为 (largefrac{n ^ 2}{d}),换块时,右端点最多移动 (n) 次,最多换 (largefrac{n}{d}) 次块,复杂度为 (largefrac{n ^ 2}{d})。
带修莫队
多了一个修改的操作,(应该是单点修改,窝还没见过区间修改的),改了一个点可以当做将一个数移除并加入了一个数,类似普通莫队也可以暴力来做。
回滚莫队
在当前已知的区间中要加入元素或移除元素来得到新的答案,可能其中一种操作很难做到 (O(1))。
那么就想到都变成一种操作。对于一个块内的 (O(d)) 来统计答案。
如果移除元素不好做就按下面的方式排序
struct query {//存询问的结构体
int x, y, id;
friend bool operator < (query q1, query q2) {
if (num[q1.x] != num[q2.x]) return num[q1.x] < num[q2.x];
return q1.y < q2.y;
}
}q[MAXN];
询问的左端点和右端点不在一个块内的话,对于左端点在同一块内的这一类型的询问,右端点一定递增,左端点所在的块暴力计算 (O(d)),右端点的可以继承所以是 (O(n))。
按照这个思想,每一个块,左端点应该设为 (min(num[q[i].x] imes d, n) + 1),右端点应该设为 (min(num[q[i].x] imes d, n))。
如果加入元素不好做就按照下面的方式排序
struct query {//存询问的结构体
int x, y, id;
friend bool operator < ( query q1, query q2) {
if (num[q1.x] != num[q2.x]) return num[q1.x] < num[q2.x];
return q1.y > q2.y;
}
}q[MAXN];
询问的左端点和右端点不在一个块内的话,对于左端点在同一块内的这一类型的询问,右端点一定递减,左端点所在的块暴力计算 (O(d)),右端点的可以继承所以是 (O(n))。
按照这个思想,每一个块,左端点应该设为 ((num[q[i].x] - 1) imes d),右端点应该设为 (n)。
看几个题
P5906 【模板】回滚莫队&不删除莫队
题解
AtCoder 1219 歴史の研究
上面俩是删除操作不好搞的。
P4137 Rmq Problem / mex
这个是加数的操作不好搞的,需要卡卡常。
树上莫队
用欧拉序将树上变成序列上
想不到这么短吧,有时间再改