zoukankan      html  css  js  c++  java
  • 分块 笔记

    分块 笔记

    一个是人都知道的毒瘤ds

    一些约定

    有一个一直出现的叫法,叫 “cnt数组”,是我喜欢这样叫,也不知道对不对。它是维护值域的,(cnt(i)) 表示有多少个值等于 (i)

    啥是分块

    观察这样一件事,我们现在要在序列上单点修改, 并维护区间和

    如果我们直球的做,修改 (O(1)), 而询问是 (O(n))

    如果我们直球的维护前缀和, 修改 (O(n)),而询问是 (O(1))

    第一种方法我们相当于把每个数独立开来看,第二种方法我们相当于把所有数放在一块看(前缀和相当于干这个事情)。

    有没有一种折中的方法,平衡两个复杂度? 先不说线段树,树状数组

    我们可以考虑设置一个阈值 (B),每 (B) 个看成是一

    对于一次区间上的操作,覆盖到的完整的一块,成为“完整块”;不完整的,称为”散块“。如下图,红色是完整块,而绿色是散块(摘自lxl的分块ppt)

    image-20210726170146226.png

    完整块的总数才 (n/B),其数量不会超过 (n/B);而散块的数量也显然小于 (2B)

    如果我们取 (B=sqrt{n}),两块就平衡了,复杂度就都是 (O(sqrt{n})) 的。当然,有些题目的数据不同,可能需要自己调整 (B) 的大小。

    这样做的好处是,对于散块,我们可以一个一个搞,很方便处理;对于完整块,可以进行整体操作,也很方便,以此支持一些复杂的操作(很多操作甚至不能线段树,树套树,之类的)。

    以上,便是对分块的一个初步认识

    它有一些有意思的别名,后文中说 “sqrt technology”,或者是 “块速”,都是指这个

    思想

    其最主要的思想便是 平衡,设一个阈值,把两种复杂操作的复杂度平衡起来。

    和中庸的思想类似

    它其实也有分治的意思,把原问题分解成了一堆小问题。所以也可以认为分块是一种类似线段树的分治结构,如图(同样是lxlppt里的),是一颗只有三层的 (B) 叉树。

    image-20210726170557794.png

    一些应用

    来点题!

    经典题:区间最小众数

    如题,每次给一个区间,求出现次数最多的数是哪个;如果有并列最多的,输出数值最小的那个

    loj的6285,洛谷的5048,都差不多这个题意(洛谷那个要求输出次数)

    按照上述分块的思想考虑,对于整块,我们可以直接预处理出这一块内部哪个最多

    但是我们有一堆整块要合并,咋办呢?暴力合并的复杂度似乎不太对(因为要合并整个 cnt 数组)

    小 技 巧:当发现一堆整块要合并而不好合并的时候,可以把它预处理出来,复杂度 (O(nsqrt{n}))

    预处理一般就枚举块的区间 (有 (O(sqrt{n}^2)=O(n)) 个),然后可以递推什么的

    那我们考虑预处理出来,(F(l,r)) 表示第 (l) 个块到第 (r) 的块中出现最多的是哪个数,顺便再记一下出现多少次。

    那查询的时候就把中间的整块,啪的一下做完了

    那散块的答案咋更新呢?我们发现我们并不能很快的维护出整块里面的 cnt 数组。可以考虑这样:

    假设当前最多 (C) 次。对于左边的散块,看看它右边(算自己)能不能有 (C+1) 个数,如果能有,就给 (C) 增加 (1)。右边同理。

    这个 “能不能有 (C+1) 个数” 可以这么做,对于每种值,维护它出现的位置数组(开vector/用new动态申请),然后(以左边为例)看看它出现的下 (C) 个位置是否还在 (r) 之前即可。这样判断是 (O(1)) 的,预处理也是 (O(n)) 的,很快啊

    记每个值的出现位置数组这个trick,在刘汝佳的蓝书中有提到,是一个很经典的trick,如果没见过没关系,见多了就知道了

    那这样一来,总复杂度就是 (O(nsqrt{n}+Qsqrt{n})) 了。

    总结:

    • 分块之后,只有 (O(sqrt{n})) 个块,因此我们可以对这些块做一些在线段树上不好做的操作,或者说是更加暴力的操作,从而更好的支持各种各样的功能

      比如说我们可以直接 (O(B^2)=O(n)) 的记录块两两之间的信息

    • 分块问题中,又要注意整块的处理,又要注意小散块的处理

    • 当我们发现一个问题用线段树/树套树根本没法搞的时候,可以考虑sqrt tech

    codechef FNCS

    题目名称看成Chef and Cthulhu

    我们发现这个题的修改十分阳间,询问十分阴间

    此时可以利用分块中 平衡 的思想,去加快查询的速度

    对于询问,我们直接在函数上分块。散块就暴力加一下,考虑能否块速的维护整块的和。

    既然我们想块速维护,最直接的想法就是,记 (fs(i)) 表示第 (i) 块里函数值的和。

    这里的fs并不是指flandre scarlet

    考虑修改(看成是加操作)一个位置对一个块里的函数有多少贡献系数。这个可以直接预处理出来,对于一个块里面,我们用一些差分等区间加技巧,就可以求出 (a) 序列中每个位置对这一块的影响系数。设 (co(i,p)) 表示 (p) 位置对第 (i) 块的贡献,那我们单点修改 (p) 增加 (x) 的时候,枚举一个块 (i),把 (x imes co(i,p)) 加过去就行了。

    现在还遗留下一个问题,对于散块,我们需要块速支持求区间和。事实证明树状数组的一个 (log) 已经过不去了。

    然而我们前面已经带一个根号了,这会还用啥树状数组啊,用sqrt tech:对 (a) 我们也分块,记一下块之间的前缀和,和每一块内部的前缀和,然后每次 (O(sqrt{n})) 的修改,以及——(O(1))查询!

    分析一波复杂度

    对于加操作:首先是去分块维护 (a) 的和,这部分是 (O(sqrt{n})) 的;然后是去贡献一整块的函数,也是 (O(sqrt{n}))

    对于询问:整块是 (O(sqrt{n})) 的遍历求和,散块是 (O(sqrt{n}) imes O(1)) 的求和,这里的 (O(1)) 是上面的块速求和

    综上,复杂度是 (O(msqrt{n})) 的,一个 (log) 都不带。

    树状数组:别骂了别骂了

    总结:

    • 尽管分块结构是带根号的,但是当询问与修改的规模不同,需要特别快的支持其中一个,而另一个则允许更慢的时候,分块还有着特殊的意义

    • 当我们发现一个操作阴间而另一个操作阳间的时候,我们可以使用分块,平衡二者的复杂度

      如本题:我们把单点修改操作从直球的 (O(1)) 变成了 (O(sqrt{n})) 的,而加快了区间求函数和的操作

    莫队

    这个再经典不过了。就是利用分块的结构,优化区间移动的步数到根号。

    那随便来几个经典题讲一下吧

    (dark)bzoj 3809

    我们发现不仅要数颜色,颜色还带了一个区间限制。

    那我们肯定想维护一个 cnt 数组的前缀和。但是我们在莫队的过程中要不断的资瓷单点加,一共有 (O(nsqrt{n})) 次加操作,而查询操作只有 (O(m)) 次。

    此时,结合上一个题的想法:我们分块!我们可以块速的支持单点加,(O(1));而查询就算变成 (O(sqrt{n})),总复杂度也是对的,是 (O(nsqrt{n}+msqrt{n}))

    具体的讲就是,我们直接维护这个 (cnt) 数组,并记一个数组 (col(i)),表示 (cnt) 数组的第 (i) 块里,有多少个位置非 (0)

    注意一下,(cnt)(col) 都是设在值域上的数组

    对于修改,直接在 (cnt) 数组上改,然后看看对 (col) 数组有没有影响,显然 (O(1))
    对于查询,先把 (col) 按整块加一下,然后散块暴力看看是否非 (0),算一算,显然 (O(sqrt{n}))

    然后就是套一个莫队,就做完了。

    这里算是一个 trick,在莫队的过程中,我们也可以用分块来平衡它询问和加一个数的复杂度

    Ynoi 2017 由乃的玉米田

    这题是典型的,莫队+bitset

    一般维护数种类,或者数之间的关系,用bitset可能会很好做

    莫队之后,动态维护区间的数组成的bitset

    对于减的查询,就把bitset给左移一下和自己求个就行了。对于加的查询,需要维护一个反过来的bitset,然后再求交 (其实这算是一种另类的卷积)

    对于乘查询是最sb不过的,枚举因数,(O(sqrt{n}))

    除的查询则是这个题的精髓:根号分治!

    这也算是一种平衡复杂度的方法。设两个数的商被要求为 (d),我们分类讨论

    如果 (d) 比较大,大于 (sqrt{n}),说明小的那个数范围不大,小于 (sqrt{n}),暴力枚举一下就行了

    如果 (d) 比较小,脑子一想,诶,不会做。

    但是我们发现 (d) 的范围小,其实我们可以先暴力枚举 (d) ,然后 (O(n)) 的扫一遍。对于当前扫到的 (a),我们看一下上一个 (a imes d)(dfrac{a}{d})(a)(d) 倍数)在哪,然后对于当前区间,看看它在不在左端点里面就行了。

    至此,我们解决了四种询问。

    总结:

    • 莫队和bitset配合,能很好的解决区间的种类问题

      如果不强制在线,它是区间数颜色的一种好做法之一,虽然那个可以主席树

    • 根号分类讨论可以做到平衡复杂度

    [Ynoi2016] 掉进兔子洞

    果然练sqrt-tech还是要找ynoi

    我们发现这个东西,其实就是要减去三个多重集的交。具体的说,对于同一种数,假设分别出现了 (c_1,c_2,c_3) 次,那它被删去的次数就是 (min(c_1,c_2,c_3))

    接下来问题便乘,要去三个多重集的交。

    如果是三个集合的交那我们会做,三个bitset来&一下就行了

    那如果是多重集,怎么做?

    这里是一个神秘trick:min/max与交/并的转化

    我们在证明min-max容斥的时候其实就用了这个转化,是一个很有用的转化,因为min/max的性质与交/并的性质其实是类似的

    即,我们把一个数 (x) 看成是集合 (1,2,3...x),那 (min) 就变成了集合的交,(max) 就变成了集合的并

    这题里相当于是求三个 (cnt) 数组的 (min),有很多位置;我们可以用计数排序里类似的思路,对 (cnt) 数组求一个前缀和,然后分配一下空间

    然后就对三块区间分别做一遍莫队,最后求一个&就行了

    然后注意到本题卡空间,所以可以把询问一万个分一组来做,一共要做10次;就当每次是一个新问题就行了,然后每次记得清空一下。这样其实会变慢,但是空间少 (10) 倍,是典型的时间换空间。

    树分块

    首先分块的本质相当于是标记一些关键点,使得它们分布的比较匀称,点有 (n/S) 个,间距都在 (S) 以内,然后我们把点与点之间的间隔看成是“块”,然后再做散块和整块的处理。

    这样,如果我们能在树上比较匀称的标关键点,那我们也可以实现树上的分块。对于一条路径,它肯定被一些关键点切开,然后和序列上一样,整块直接搞,散块暴力搞,就行了。

    但是树上有两条路径,可能要考虑一些去重方面的问题,或者是更复杂的维护 —— 毕竟有四块散块。

    我暂时只会这样一种分块方法:假设块大小是 (S),然后去标记关键点。每次找一个最深的,没被标记的点,如果它上面 (S) 个都没被标记,就标记它上面数 (S) 条边(即 (S) 级祖先)那个点。

    这样一标记,显然点之间的间隔是 (O(S)) 的;标记一个点至少会让 (S) 个点再也无法标记,于是顶多有 (n/S) 个点,满足条件。

    对于洛谷的板子题,就在路径的块上,维护区间的bitset;对于散块,就暴力加入bitset;然后bitset的count就是答案了。

    后记

    感觉下午4,5点钟这个特别困的时候, 特别适合写blog, 毕竟打不动题

    现在暂时还没学多少内容,只是简单的入门一下,东西少,抱歉qaq

    其实我也是啥都干不动了,来写点blog总结一下,假装自己很努力

    最后,

    都2020年了,还有人考分块?

  • 相关阅读:
    MySQL查看视图
    MySQL创建视图(CREATE VIEW)
    Mysql视图
    Snipaste使用教程
    Mysql全文检索
    MySQL中MyISAM和InnoDB
    MySQL卸载注意事项
    MySql免安装配置(Windows)
    验证用户名密码:Servlet+Maven+Mysql+jdbc+Jsp
    使用response下载文件
  • 原文地址:https://www.cnblogs.com/LightningUZ/p/15063126.html
Copyright © 2011-2022 走看看