分块[update,可以和原本的yxc板子对比]

分块

我发现,要学莫队,分块还是得学好。上一次写的分块的博客太简单,这一篇补一下。

分块是一种思想,把一个整体划分为若干个小块,对整块进行整体处理,对零散块进行单独处理。本文主要介绍块状数组,利用分块思想处理区间问题的一种数据结构。

块状数组将一个长度为 (N) 的数组划分为 (sqrt{N}) 块,这样,对于任意的查询区间 ([l,r]),我们可以将其拆分为最多不超过 (sqrt{N}) 块和最多不超过 (sqrt{N}) 个零散区间。总体复杂度为 (O(sqrt{N}))

显然,分块的时间复杂度比不上线段树BIT这些对数级算法,但由此换来的,是更高的灵活性。与线段树不同,块状数组支持维护不满足结合律的信息,也不需要一层层传递标记。举一个例子,我们需要查询任意区间 ([l,r]) 的重复次数不少于 (3) 的数字个数,如果用线段树维护,无法用一个时间复杂度少于为 (O(N)) 的函数 (f) 来求解 (ans[l,r] = f(ans[l,mid], ans[mid+1,r]))

因此我们需要引入块状数组。事实上,块状数组也可以看作一棵高度为 (3) 的树。

截屏2021-04-29 上午8.26.42

1. 预处理 (O(N))

具体地,使用块状数组,我们需要划定每一个块所占据的范围、每个数的分块编号。

const int N = 1e6+5;
const int sqN = 1e3+5;

int n, sq;
int st[sqN]; // st[i] 表示i号块的第一个元素的下标
int ed[sqN]; // ed[i] 表示i号块的最后一个元素的下标
int id[N]; 	 // id[i] 表示元素i的分块编号

// 维护信息
int arr[N]; 	// 零散数组
int sum[N];		// 分块和数组
int mark[N];	// 分块公共加数组

void init(){
  	sq = sqrt(n);
  	for(int i = 1; i <= n; ++i){
      	st[i] = n / sq * (i-1) + 1;
      	ed[i] = n / sq * i;
    }
  	ed[sq] = n;
  	
  	for(int i = 1; i <= sq; ++i){
      	for(int j = st[i]; j <= ed[i]; ++j){
          	id[j] = i;
        }
    }
}

2. 修改 (O(sqrt{N}))

对于待修改区间 ([l, r]),当 (l)(r) 在同一块时,直接暴力修改零散数组sum数组。如果不在同一块,则先处理 ([l, ed[id[l]]])([st[id[r]], r])这些零散点,然后再处理 ([id[l]+1, id[r]-1]) 这些分块。

但是应该注意一点是:只有在修改零散点时去修改零散数组和sum数组。如果对于整个分块操作,应该只修改mark数组而不修改sum数组。这一点很重要,不要在操作整个分块的时候,令sum[i] += size[i] * x,否则就会重复计算

void modify(int l, int r, int x){
  	if(id[l] == id[r]){
      	for(int i = l; i <= r; ++i){
          	arr[i] += x;
        }
      	sum[id[l]] += (r-l+1) * x;
    }else{
      	for(int i = l; i <= ed[id[l]]; ++i){
          	arr[i] += x;
        }
      	sum[id[l]] += (id[l]-l+1) * x;
      	
      	for(int i = st[id[r]]; i <= r; ++i){
          	arr[i] += x;
        }
      	sum[id[r]] += (r-id[r]+1) * x;
      
      	for(int i = id[l]+1; i < id[r]; ++i){
          	mark[i] += x;
        }
    }
}

3. 查询 (O(sqrt{N}))

查询和修改的基本思路相同,注意一点,无论是操作零散点还是整个分块,都需要加上mark数组。

int query(int l, int r){
  	int ans = 0;
		if(l == r){
      	for(int i = l; i <= r; ++i){
						ans += arr[i];
        }
      	ans += mark[id[i]] * (r-l+1);
    }else{
      	for(int i = l; i <= ed[id[l]]; ++i){
          	ans += arr[i];
        }
      	ans += mark[id[l]] * (ed[id[l]]-l+1);

      	for(int i = st[id[r]]; i <= r; ++i){
						ans += arr[i];
        }
     		ans += mark[id[r]] * (r-st[id[r]]+1);
      	
      	for(int i = id[l]+1; i < id[r]; ++i){
          	ans += sum[i] + mark[i] * (ed[i]-st[i]+1);
        }
    }
  	return ans;
}
---- suffer now and live the rest of your life as a champion ----
原文地址:https://www.cnblogs.com/popodynasty/p/14721259.html