浅谈数列分块问题

Posted inversentropir-36

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈数列分块问题相关的知识,希望对你有一定的参考价值。

写这篇博文呢,主要还是为了准备集训队员交流,毕竟分块是我最喜欢的数据结构,所以我就试着写了一篇博文。


基本介绍:

分块是维护较为复杂的信息的,尤其是不满足区间可加性可减性的信息的重要工具,代码也非常的麻烦和不直观,Debug 可以 Debug 一天。而分块是以一种“暴力”的思想来维护信息的。其基本思想就是通过把序列划分为多个小块(块长一般为 (sqrt n) )并预处理出一部分信息保存下来,从而达到维护信息的一种数据结构。分块直观,通用,容易理解和实现。接下来我将以几道例题来谈谈分块。

[Example 1] A Simple Problem with Integers

给定长度为 (N(Nle 10^5)) 的数列 (A) , 然后输入 (Q(Qle 10^5)) 行指令。

第一类指令形如 C l r d ,表示把数列中第 (lsim r) 个数都加 (d).

第一类指令形如 Q l r ,表示询问数列中第 (lsim r) 个数的和.

固然,这道题可以使用树状数组或线段树在 (O((n+m)log n)) 的时间复杂度内解决此问题,但是我们现在使用分块来求解。

我们考虑把数列 (A) 分解成 (N) 个长度为 (lfloor sqrt N floor) 的段,其中第 (i) 段左端点为 ((i-1)lfloor sqrt N floor + 1) ,右端点为 (min(ilfloor sqrt N floor,N)) ,如图所示(大约很丑)。

技术图片

像这样,我们把一个数列分成了 (sqrt n) 段。

我们可以预处理出每段的数字之和,存在数组 (sum) 里面,其中 (sum_i) 便是第 (i) 段的区间和,最后另外开一个 (add) 数组,然后设 (add_i) 为这个块上还要加的数,起初 (add_i=0)

我们分别进行处理:

  1. 区间加指令。
  • 如果 (l)(r) 在同一块 (i) 里面,暴力修改,把 (A_l,A_{l+1}···A_r) 都加 (d) 的同时 (sum_i+=d(r-l+1))

  • 要不然就让 (l) 在第 (lb) (Left Block)段,让 (r) 在第 (rb) (Right Block)段。首先对于 (i in [lb+1,rb-1]) ,使 (add_i+=d) 。然后两端朴素更新。

  1. 区间查询指令。
  • 如果 (l)(r) 在同一块 (i) 里面,暴力查询,答案是 (sumlimits^{r}_{i=l}a_i+add_i(r-l+1))

  • 要不然就让 (l) 在第 (lb) (Left Block)段,让 (r) 在第 (rb) (Right Block)段。首先对于 (i in [lb+1,rb-1]) ,使 (ans+=sum_i+add_i imes len_i)(len_i) 表示第 (i) 块的块长)。然后两端朴素查询。

这就是分块算法的基础——对 (add) 数组的使用。以后可能会用更多的数组来维护一个分块,我们统称其为“大分块”。不过目前的阶段没有必要碰这个毒瘤的玩意。

因为段数和块长都是 (sqrt n) ,所以整个算法的时间复杂度为 (O((n+m)sqrt n))

typedef long long ll;
ll a[100010], sum[20010], add[20010];
int L[20010], R[20010];      //左右端点
int pos[100010];             //每个位置属于哪一段
int n, m, t;
void change(int l, int r, long long d) {
	int p = pos[l], q = pos[r];
	if (p == q) {
		for (int i = l; i <= r; i++) a[i] += d;
		sum[p] += d*(r - l + 1);
	}else {
		for (int i = p + 1; i <= q - 1; i++) add[i] += d;
		for (int i = l; i <= R[p]; i++) a[i] += d;
		sum[p] += d*(R[p] - l + 1);
		for (int i = L[q]; i <= r; i++) a[i] += d;
		sum[q] += d*(r - L[q] + 1);
	}
}
ll ask(int l, int r) {
	int p = pos[l], q = pos[r];
	long long ans = 0;
	if (p == q) {
		for (int i = l; i <= r; i++) ans += a[i];
		ans += add[p] * (r - l + 1);
	}else {
		for (int i = p + 1; i <= q - 1; i++)ans += sum[i] + add[i] * (R[i] - L[i] + 1);
		for (int i = l; i <= R[p]; i++) ans += a[i];
		ans += add[p] * (R[p] - l + 1);
		for (int i = L[q]; i <= r; i++) ans += a[i];
		ans += add[q] * (r - L[q] + 1);
	}
	return ans;
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	t = sqrt(n);
	for (int i = 1; i <= t; i++) {
		L[i] = (i - 1)* sqrt(n) + 1;
		R[i] = i* sqrt(n);
	}
	if (R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n;
	for (int i = 1; i <= t; i++)for (int j = L[i]; j <= R[i]; j++)pos[j] = i,sum[i] += a[j];
	while (m--) {
		char op[3];
		int l, r, d;
		scanf("%s%d%d", op, &l, &r);
		if (op[0] == ‘C‘) {
			scanf("%d", &d);
			change(l, r, d);
		}else printf("%lld
", ask(l, r));
	}
}

呃,这个,大家都知道我是 (log) 分块邪教的(/ω\) 这样的块长是 (log(n) imes 5) 最坏时间复杂度是 (O((n+m)frac{n}{log n imes5})) ,在 (Q) 多的数据下效率非常不错(DarkBZOJ Top1),但是对于 (n) 多的数据就会非常坑爹。

请完成例题的代码实现:

请独立思考完成以下习题:


以上是关于浅谈数列分块问题的主要内容,如果未能解决你的问题,请参考以下文章

数列分块1-9

loj#6281. 数列分块入门 5

LiberOJ 6278 数列分块入门 2(分块)

数列分块入门7 解题报告

数列分块入门9 解题报告

数列分块入门3 解题报告