前言
如题。
值域分块
顾名思义,就是在桶上分块。
它的用处是把区间修改和区间询问中某一种操作变成 \(O(1)\),另一种变成 \(O(\sqrt n)\)。
所以经常用来辅助维护两种操作数量严重不对等的数据结构。
典型代表有莫队和根号分治。
这里看一个莫队的例子。
如我们要维护一个二维数点。
那么莫队上的操作就是 \(O(n \sqrt n)\) 次单点加和 \(O(n)\) 次区间求和。
那么单点加时维护所属块的总和,就可以在区间查询时加速对于整块的查询而做到 \(O(\sqrt n)\) 查询。
具体代码如下:
void Add(int x){
int bl=x/sq+1;
if(x%sq==0){
bl--;
}
cnt[x]++;
block[bl]++;
}
void Del(int x){
int bl=x/sq+1;
if(x%sq==0){
bl--;
}
cnt[x]--;
block[bl]--;
}
int query(int x){
int res=0;
for(int i=0;i<=400;i++){
if((i-1)*sq+1<=x&&x<=i*sq){
for(int j=(i-1)*sq+1;j<=i*sq;j++){
res+=cnt[j];
if(j==x){
return res;
}
}
}
res+=block[i];
}
return res;
}
根号重构
这个方法是用来维护诸如翻转之类的区间操作的。
首先你需要把所有点缩到一个块里。
然后对于每次操作,把操作两个端点从其所属块中断开(类似于珂朵莉树的搞法)。
然后这个问题就变成了整块下的问题。
那么现在就可以暴力去搞了。
但是注意到块很多的时候时间会退化到 \(O(n^2)\)。
所以当块的数量多余 \(\sqrt n\) 时,暴力重构所有块。
具体来说把所有块再次缩到一个块中。
这样保证了操作复杂度不会高于 \(O(\sqrt n)\)。
另外由于每次只会增加常数个块,所以重构数量是 \(O(\sqrt n)\) 的。
那么总复杂度也就只有 \(O(n \sqrt n)\)。