首页 > 其他分享 >分块入门详解(比较全面)

分块入门详解(比较全面)

时间:2023-01-18 21:00:10浏览次数:52  
标签:入门 分块 int 复杂度 sqrt 详解 操作 mathrm

最近写了几个分块,顺便做一下笔记。

什么是分块

分块是一种数据结构。。

有许多数据结构都是 \(\mathrm{log}\) 数据结构,比如线段树,树状数组等等。他们复杂度优秀,但是都是树形结构,有较大的思维难度和局限性。那么有没有什么复杂度一般,但是非树形的数据结构呢?

有的,就是分块。

分块的思路是把数据分成一个个的小块。设每块长为 \(\text{S}\)。举个栗子:

1, 1, 4, 5, 1, 4 (S = 2)

那么我们就可以把他分成

1, 1 | 4, 5 | 1, 4

我们成功把他分成了三个小块。

对于每个询问 \(\mathrm{[l, r]}\),它会跨越许多小块和整块。比如对于刚才那个数据:

1, 1 | 4, 5 | 1, 4 (l = 2, r = 5, S = 2)
   |          |
   l -------- r

l, r 跨越了第一块的后半个,第二块和第三块的前半个。

对于整块,我们一般可以用打懒标记之类的方法来搞,一共有 \(\dfrac{n}{S}\) 个整块,所以复杂度最坏也是 \(\mathrm{O(\dfrac{n}{S})}\)。对于散块来说,最多会有一左一右两个散块,长度最多为 \(\mathrm{2S}\),对他们进行暴力操作,复杂度最坏为 \(\mathrm{O(S)}\)。考虑常数,总复杂度为 \(\mathrm{O(2S + \dfrac{n}{S})}\)。(虽然 \(O\) 里面不应该加常数)。根据均值不等式,当 \(\mathrm{S = \dfrac{\sqrt{2}}{2} \sqrt{n}}\)。通常我们取 \(S = \sqrt{n}\),单次操作复杂度稳定在 \(\mathrm{O(\sqrt{n})}\) 级别。

分块怎么写

首先先说一下分块的几个重要操作:

  1. 定位操作。\(\mathrm{get(x)}\) 函数返回 \(x\) 所处的块的编号。可以这样写 int get(x) { return (int)x / S; }
  2. 查询端点。\(\mathrm{getl/getr}\),返回当前块的左、右端点。可以这样写(前提是你的 get 函数和我一样。)
int getl(int x) { return S * x + (x == 0); }
int getr(int x) { return min(n, (x + 1) * S - 1); }

数列分块入门1 举个栗子。

题意: 给定长度为 \(\mathrm{n}\) 的数列,进行 \(\mathrm{n}\) 次操作。操作涉及区间加法,单点查询。

这种题当然可以 线段树 / 树状数组,但是今天我们要用分块来写。按照上面我们说的。借鉴线段树的思路,我们可以维护一个 \(\mathrm{add}\) 懒标记,来维护当前块应该加上几。

现在假设我们修改的区间是 \(\mathrm{[l, r]}\),可能有下面几种情况。

  • 左右端点在同一个块里。我们遍历 \(\mathrm{l \sim r}\),暴力加即可。复杂度 \(\mathrm{O(\sqrt{n})}\)
  • 左右端点不在同一个块里。我们可以遍历中间的整块并打上懒标记。由于块数不超过 \(\mathrm{O(\sqrt{n})}\) 个,所以复杂度也不高。对于不在整块内的边角,直接暴力加就可以。

核心代码

int get(int x) { return x / S; } // 定位当前点在哪个块
void modify(int l, int r, int v) { // 修改操作
	int ls = get(l), rs = get(r);
	if (ls == rs) { for (int i = l; i <= r; i ++ ) w[i] += v; return; } // 在同一个整块内
	int i, j;
	for (i = l; get(i) == ls; i ++ ) w[i] += v; // 处理边角
	for (j = r; get(j) == rs; j -- ) w[j] += v;
	for (int k = get(i); k <= get(j); k ++ ) // 处理整块。
		add[k] += v;
}

int main() {
	.......

	if (query node) printf("%d\n", w[r] + add[get(r)]);
	else modify(l, r, v);
}

其他例题

数列分块入门7

题意: 给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间乘法,区间加法,单点询问。

可以对照线段树2来做。对每个块维护加、乘懒标记。注意:懒标记更新的时候注意顺序,对散块操作的时候要重构整个块。

代码示例

数列分块入门2

题意: 给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间加法,询问区间内小于某个值 \(x\) 的元素个数。

分块常用的思想,就是在每个块内维护一个有序序列。加操作时可以打懒标记。查询操作可以直接块内二分。单次操作复杂度 \(\mathrm{O(\sqrt{n} \log n)}\) ( \(\log \sqrt{n}\) 和 \(\log n\) 同阶)

代码示例

数列分块入门5

这道题带插入怎么办呢?块状链表隆重登场!!

块状链表可以看一下我写的博客 块状链表详解

剩下的就不讲了吧,再放几道例题。

例题

  1. 数列分块全系列

  2. 某些线段树题(比如洛谷线段树模板)

  3. 其他题目,如 Luogu 分数统计

以后可能还会加例题。先挖个坑。

标签:入门,分块,int,复杂度,sqrt,详解,操作,mathrm
From: https://www.cnblogs.com/LcyRegister/p/17060561.html

相关文章

  • 【OI】值域分块入门
    相信很多人在学习莫队,刷莫队题目时,回不可避免的遇到一个数据结构——值域分块。这篇文章就是帮助各位快速入门的。Q1给定一个序列,实现单点修改以及区间查询,保证修改次......
  • pikachu-CSRF跨站请伪造通关详解
    pikachu-CSRF跨站请伪造通关详解一、getburp抓包修改信息页面,可以看到请求的url修改的数据都在url中,我们可以尝试在url中修改,之后再去访问url此时的数据内容:将url中......
  • Unity3D入门基础知识
    一、基础概念1、物体与空物体物体(GameObject),其实是一个节点或容器。一般所谓的“物体”,即有形状的东西,对应的Mesh,网格信息代表了物体(形状)。空物体(EmptyObject),即空对象......
  • C++入门篇之重载运算符和重载函数
    C++允许在同一作用域中的某个函数 和运算符 指定多个定义,分别称为函数重载 和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声......
  • C++入门篇之C++ 指针
    学习C++的指针既简单又有趣。通过指针,可以简化一些C++编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的C++程序员,学习指针是......
  • c++入门篇之C++ 多态
    多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数......
  • c++入门篇之C++ 预处理器
    预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是C++语句,所以它......
  • c++入门篇之C++ 引用
    C++引用引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。​​C++引用vs指针​​引......
  • c++入门篇之C++ 信号处理
    C++信号处理信号是由操作系统传给进程的中断,会提早终止一个程序。在UNIX、LINUX、MacOSX或Windows系统上,可以通过按Ctrl+C产生中断。有些信号不能被程序捕获,但是下......
  • 自学开发技术,从入门到入行
    今​天我们不谈技术,也不聊业务,说说学习技术的心得。说到学习这种事情,无论是学什么,都需要持之以恒,拥有坚持的决心才有可能会学到一些东西。如果只是三天打鱼,两天晒网的态度,不......