【OI】简单的分块

介绍下简单的分块:

当我们遇到区间类问题的时候,如何保证我们快速而高效地完成操作?

答案是线段树分块。

所谓分块,就是把一个序列分成许多块分别维护。是不是想起了树状数组 这样能大大提高效率:

例如,我们需要查询l,r中所有元素的和

利用分块,我们可以把1 2 3 4 5 6 7 8 9 10

分为

[1 2 3] [4 5 6] [7 8 9] [10]

比如,我想要3~10的元素和。这是,我们拿出3~10的区间:

...[3] [4 5 6] [7 8 9] [10] 

而我们已经预处理了 [4 5 6]与 [7 8 9]的区间和,我们只需要算出两端的和就可以。这样,就可以大大提高查询的速度。

#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxN = 100010, MaxB = 1010;

int n, B, q, Cnt;
int a[MaxN], Left[MaxB], Right[MaxB], Pos[MaxN];
long long sum[MaxN];

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	// 分块预处理 
	B = max((int)sqrt(n), 1);
	for (int i = 1; i <= n; i++)
	{
		if (i % B == 1 % B) Cnt++; // 每块块首 Cnt++ 
		Pos[i] = Cnt; // i 处在哪一块 
		if (Left[Cnt] == 0) Left[Cnt] = i; // 第Cnt块的左边 
		Right[Cnt] = i; // 第Cnt块的右边
	}

// for (int i = 1; i <= n; i++) printf("%d ", Pos[i]); puts("");
// for (int i = 1; i <= Cnt; i++) printf("%d %d\n", Left[i], Right[i]);

	for (int i = 1; i <= Cnt; i++)
		for (int j = Left[i]; j <= Right[i]; j++)
			sum[i] += a[j];	// sum[i] 表示的是第i块的和 

	// 处理询问 
	scanf("%d", &q);
	//	a[l] + a[l + 1] + ... + a[r] 变成 sum[r] - sum[l - 1]
	//	这样我们可以只用求 l=1 的情况 
	//	但是我现在不这样做 
	for (int i = 1; i <= q; i++)
	{
		int l, r;
		scanf("%d %d", &l, &r);
		long long ans = 0;
		if (Pos[l] == Pos[r])
		{
			// 1   l,r 在同一个块内
			// [  l     r  ]
			for (int j = l; j <= r; j++) ans += a[j];
		}
		else
		{
			// 2   l,r 不在同一个块内
			// [  l   ] [      ] [       ] [r      ]
			for (int j = l; j <= Right[Pos[l]]; j++) ans += a[j];
			for (int j = Right[Pos[l]] + 1; j <= Left[Pos[r]] - 1; j++) ans += sum[j];
			for (int j = Left[Pos[r]]; j <= r; j++) ans += a[j];
		}
		printf("%lld\n", ans);
	}
	return 0;
}

(不是我写的,但是的确十分简洁易懂对吧)

猜你喜欢

转载自www.cnblogs.com/dudujerry/p/10337181.html
OI
今日推荐