对于有些区间问题,数据结构难以维护,但是若已知 ans(l,r),可以很快地求出 ans(l±1,r) 与 ans(l,r±1),即可以很快地在询问区间中增删一个数,我们容易想到求出 ans(li,ri) 之后,通过一步一步挪动 l,r 得到 ans(li+1,ri+1)。
为了使挪动次数尽可能小,莫队提出:设块长为 B,block(x)=⌊Bx⌋,可以对询问以 [block(l),r] 为关键字排序。B=mn 时,有最小复杂度 O(nm)。
封装
可以将从询问范围中增删数封装为一对互逆的函数 add
和 del
,函数的参数有下标/值两种写法,为了兼容性,我们这里全都要!采用 add(p, x)
的写法。可以根据实际,采用不同的写法。
在带修莫队中能更好地体现“互逆”的重要性。
优化
一个小优化:block(l) 为奇/偶时把 r 升/降序排列,这样可以减小挪动次数,减小常数。
假如有修改呢?
考虑分版本,在询问上记录下当前询问的版本号。
记录上一个版本至此版本的更改。(如 R[t] = {p: 3, x: 6}
表示从版本 t−1 到版本 t 的更改为 a3←6)
请注意,由于赋值操作不可逆,需要用 swap(R[x].x, a[R[t].p])
代替 a[R[t].p] = R[t].x
。
图示:
ia11213445R2={p:3, x:6}版本1,R2记录1→2t++; swap(R[t].x, a[R[t].p]);swap(R[t].x, a[R[t].p]); t--;ia11213645R2={p:3, x:4}版本2,R2记录2→1
封装
void upd(int l, int r, int t){
if (l<=R[t].p&&R[t].p<=r) {
del(R[t].p, a[R[t].p]);
add(R[t].p, R[t].x);
}
swap(R[t].x, a[R[t].p]);
}
即可调用 upd(++t)
和 upd(t--)
。
此时以 [block(l),block(r),t] 为关键字排序,取 B=n2/3 时可以在 n,m,t 同级时达到 O(n5/3) 的时间复杂度。