分桶法:给一个n个元素的数组a[0...n],分桶法的思想是将这n个元素进行均分,均分成√n组,每组里有√n个元素,这里“组”的意思与“桶”等价。这种分法也称之“平方分割”。
这样会带来哪些处理上的好处呢?其实类似于线段树,分桶法的思想也是每个桶分别维护各自桶内部的信息,然后在处理区间问题上带来快捷。这里以RMQ(Range Minimum Query)为例:
给出一个n个元素的数组a[],我们的任务有两个:
- 给定l,r,我们要答出区间[l,r]上的最小值;
- 给定i,x,我们要将a[i]替换成x.
这里我们用b代表”桶宽“,取b=[√n], 然后依次在每个桶中放入b个元素:
b=(int) sqrt(n); vector<int> bucket[MAX_N/b]; for(int i=0;i<n;i++) { bucket[i/b].push_back(a[i]); }
每个桶所维护的便是它的区间里的最小的数,记为val[i]
任务1:查询
- 若所查询的区间[l,r]完全的包含了桶bucket[i],则直接查询桶bucket[i]所维护的那个值;
- 若所查询的区间[l,r]与桶bucket[i]有交集但并不完全包含该桶(或者说桶i仅有一部分在[l,r]中),这时我们便遍历该桶在查询区间[l,r]中的那一部分。
而查询区间[l,r]时,我们需要遍历的元素个数不超过n/b个,这部分的复杂度是O(√n);而询问各个桶维护的最小值所要的复杂度也是O(√n),所以查询时的总的复杂度是O(√n)。
任务2:更新
- 更新元素时,直接遍历一遍更新后的桶中的值,得出最小值即可
//值得注意更新时不可以采用x与桶k维护的值val[k]直接比较取min{val[k],x},因为桶不知道替换掉的是谁,可能是val[k]所在的a[i]被x替换了(不过感觉大家不会出这种错啦~
更新时仅需遍历一个桶,因此其复杂度也是O(√n)。
这样,如果总的操作数是m,那么总的操作复杂度就是O(m√n),条件不苛刻是还是推荐使用的,毕竟实现起来比线段树简单。