如何优雅的暴力——分块

前言#

近期也是把hzwer的数列分块入门肝完了,感觉分块很玄学(

什么是分块#

分块的基本思想是,通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。

分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度。

分块是一种很灵活的思想,相较于树状数组和线段树,分块的优点是通用性更好,可以维护很多树状数组和线段树无法维护的信息。

当然,分块的缺点是渐进意义的复杂度,相较于线段树和树状数组不够好。

不过在大多数问题上,分块仍然是解决这些问题的一个不错选择。

分块的巧妙之处在于对大块通过预处理后能够实现快速对区间信息进行修改,零散块暴力,大块整体修改,使得分块虽然看起来是暴力,但是时间复杂度是 O(nn) 的。

分块一般要维护的信息#

我个人的习惯是维护每个块的头和尾,每个点所在块的编号,以及每个块的标记。
关于块长 len 一般是把它设为 n ,具体的数值应该根据题目的数据范围来定。

分块的预处理#

定义 len 为每个块的长度,num 为块的数量,不难得到

int len = sqrt(n), num = n / len + (n % len ? 1 : 0);
for(int i = 1;i <= num;i ++)
	t[i] = ed[i - 1] + 1, ed[i] = st[i] + len - 1;
for(int i = 1;i <= num;i ++)
	for(int j = st[i];j <= ed[i];j ++)
		pos[j] = i;

下面以LOJ的数列分块入门为例。

数列分块入门1#

题意:区间加,单点查询。

很裸的题目,有各种方法做,我这里选择分块。

考虑对每个块维护一个标记 tag ,修改时遵循零散块暴力,大块从修改标记的原则。

举个栗子:

     1  2  3 | 4  5  6 | 7  8  9 | 10
a  : 0  0  0 | 0  0  0 | 0  0  0 | 0
pos: 1  1  1 | 2  2  2 | 3  3  3 | 4
tag:    0    |    0    |    0    | 0

当我们要将区间 [3,7] 全部加上 114514 时,我们的过程应该是这样的:
1.先将左边零散块 [3,3] 进行暴力修改。

     1  2    3    | 4  5  6 | 7  8  9 | 10
a  : 0  0  114514 | 0  0  0 | 0  0  0 | 0
pos: 1  1    1    | 2  2  2 | 3  3  3 | 4
tag:    0         |    0    |    0    | 0

2.将中间大块的 tag 增加 114514

     1  2    3    | 4    5     6 | 7  8  9 | 10
a  : 0  0  114514 | 0    0     0 | 0  0  0 | 0
pos: 1  1    1    | 2    2     2 | 3  3  3 | 4
tag:    0         |    114514    |    0    | 0

3.将右边零散块 [7,7] 进行暴力修改。

     1  2    3    | 4    5     6 |    7    8  9 | 10
a  : 0  0  114514 | 0    0     0 | 114514  0  0 | 0
pos: 1  1    1    | 2    2     2 |    3    3  3 | 4
tag:    0         |    114514    |         0    | 0

此时我们的修改就完成了,如果我们想查询 a4 的话,就输出 a[4]+tag[pos[4]] 就行了。
code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 5e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int n;
int a[N];
int pos[N], block[N], st[N], ed[N], tag[N];
void change(int l, int r, int c)
{
	if(pos[l] == pos[r])
	{
		for(int i = l;i <= r;i ++)
			a[i] += c;
	}
	else
	{
		for(int i = l;i <= ed[pos[l]];i ++)
			a[i] += c;
		for(int i = pos[l] + 1;i <= pos[r] - 1;i ++)
			tag[i] += c;
		for(int i = st[pos[r]];i <= r;i ++)
			a[i] += c;
	}
}
int main()              //主函数
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for(int i = 1;i <= n;i ++)
	 	cin >> a[i];
	int len = sqrt(n), num = n / len + (n % len ? 1 : 0);
	for(int i = 1;i <= num;i ++)
		st[i] = ed[i - 1] + 1, ed[i] = st[i] + len - 1;
	ed[num] = n;
	for(int i = 1;i <= num;i ++)
		for(int j = st[i];j <= ed[i];j ++) pos[j] = i;
	for(int i = 1;i <= n;i ++)
	{
		int op, l, r, c;
		cin >> op >> l >> r >> c;
		if(op == 0) change(l, r, c);
		else cout << a[r] + tag[pos[r]] << '\n';
	}
    return 0;
}

数列分块入门2#

咕咕咕

posted @   Svemit  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
主题色彩