zoukankan      html  css  js  c++  java
  • 分块学习

    分块

    分块(第一节)

    前记

    最近学了分块(好奇怪啊,我先学线段树,树状数组,平衡树才开始学分块),为了不让自己忘记,也随便总结总结。因为刚刚开始入门,所以只讲最简单和最基础的。(以后会慢慢更新)

    啥是分块妳

    嗯~,其实分块是一种很暴力的算法,可以用于处理区间操作(主要)等问题,可以将线性的枚举优化。分块,就是分很多块来进行处理,和分治挺像的,不过分治分了再分,每次分两块(或更多),而分块只用一次分很多块就可以了。

    分块的作用

    上面已经说了,分块是用来优化枚举的,那怎么优化咧,就拿P3374 【模板】树状数组 1来和大家讲一下。

    暴力做法

    对于每个操作,我们直接将为该下标的数加x 。

    对于每一个讯问,我们一个for过去统计。

    这样这道题就做完了,可是这样的时间复杂度非常高,是O(NM).

    分块做法

    正解树状数组,不过这里讲分块。我们想一下怎么优化呢,对于每个操作,我们的复杂度是O(1)的,十分快;但是,对于每个讯问,复杂度却很高。那我们就从讯问入手,降低复杂度。

    我们可以预处理每一个区间的和,前缀和就可以了,那修改操作会改变前缀和,怎么办呢?我们就可以引用分块思想,把这整个区间分成sqrt(n)块,将每一块的和先预处理出来,记为tag,那么每次操作,我们将要操作的那个数的所属块tag+=x,同时也对那个数直接修改。

    讯问的话,我们查找[l,r]的区间和,如果有哪些块在[l,r]内的话,我们可以直接累加该块的tag。但有时候,区间[l,r]总有些数不完全是一整个块,那么我们暴力将那些数累加,每一次时间复杂度为O(sqrt(n))。

    时间复杂度证明

    你说分块时间复杂度是O(sqrt(n))就一定是啊,那肯定不行,于是我们需要证明。

    1、为什么要分成sqrt(n)块

    理论上来说,这样分是最优的,为什么,因为如果分太多的话,直接累加块的tag的时候会变慢;有如果,分太少的话,暴力累加数的值会变慢;所以分成sqrt(n)块是最优的。

    2、为什么讯问的复杂度是sqrt(n)的呢
    讯问分成两个部分:

    (1)将整个块累加:块也就sqrt(n)个,所以最多sqrt(n)的复杂度。

    (2)暴力将一个个数累加:如果是散着的数的话,数量一定小于一个块的大小,一个块的大小是n/sqrt(n)=sqrt(n),所以时间复杂度最多是sqrt(n)(ps:要对区间两边都累加,所以有常数2)

    那么总时间复杂度就是O(3 sqrt(n))。是不是快很多了呢!

    练习

    一样的【模板】树状数组 2也可以这么做。

    对于每个操作,分块将区间[l,r]的整块tag+=x(tag初始为0),再暴力将区间[l,r]两边不完全属于一个块的数累加值(tag不需累加)。

    对于每个讯问,直接输出(该数的值+该数属于的块的tag值)就行了

    Loj #6277. 数列分块入门 1

    Loj #6277. 数列分块入门 1代码

    建议

    最好去Loj上刷分块,那里有分块专题,还可以下数据Loj分块地址

    分块(第二节)

    前记

    经过第一节的学习,大家应该都明白分块的基本思想了吧。下面第二节我将通过Loj上的分块专题来讲分块。

    开始学习

    先看一下这道题:数列分块入门 2

    一看题目,咦,求[l,r]区间小于c^2的数字个数,这不是很像求[l,r]区间c^2的排名吗,马上就可以想到平衡树啦,可是平衡树这么难写,那怎么办? 分块!

    首先,我们一个一个步骤分着来看:

    (1).操作 操作和 分块1 的操作一模一样,我们先不理。

    (2).查询 如果一般查询排名,我们可以将这一段数截出来,排序,然后二分查找(lower_bound操作)就ok了。那我们可不可以先将每一个块的数都排好序,然后查找每一个块的时候就直接二分呢? ofcourse,当然可以!那零零散散的剩下的非整块的数就暴力找就可以了。

    (3).排序操作 我们查询需要先排序,那么我们排序也成为了一个操作。我们另外开一个数组ve[i](ve[i]为vector,i表示第i个块)来存每个块排序的数据(这里我使用了vector)。到时候查询的时候直接二分ve[i]就可以了。那每一次零散修改操作都会修改数值(整块操作还是直接加tag),那么我们要对有修改数值的那个块重新进行块内排序。

    那么我们这个分块的基本思想就是这样了。还有很多小细节可以看看我的代码:
    #6278. 数列分块入门 2代码

    练习

    那么好,如果你学会了分块2并理解了,那么分块3对你来说简直是小菜一碟。

    如果你真的不会的话(前提会分块2),我可以告诉你只需要修改讯问部分,如果你真的不行的话,说明你还没有真正理解分块2,再去看看吧下面,分块3代码(除了讯问部分我其他都不加注释,因为和分块2一样):#6279. 数列分块入门 3代码

    分块(第三节)

    前记

    前面,我们学习了分块的许多操作,分块可以代替树状数组和平衡树的部分操作,那么线段树可不可以代替呢?

    分块的力量是巨大的,连平衡树都可以,小小的线段树怎么可能不行呢?这一节,讲线段树的分块做法!

    开始学习

    那么先看看这道题:#6280. 数列分块入门 4

    很明显,这简直是线段树的模板题(吓得我直接就用神威无敌线段树水过了),分块当然也可以完成。

    我们还是分步骤看:

    (1).修改操作 又和分块1一样,不讲。

    (2).查询操作 咦,这里是查区间和,分块1是查一个数,我们可以直接想到暴力将[l,r]区间所以数加起来,但明显超时。分块嘛,肯定是有作用嘀,我们可以马上想到提前把每个块的和都处理了,那么查询就十分方便了,两端暴力查,整块的就直接累加和就ok了。

    嗯,很明显,这程序和分块1很像,但肯定被魔改过的,主要还是看程序的注释吧:#6280. 数列分块入门 4代码

    练习

    然后我们就可以做#6281. 数列分块入门 5

    看完是不是很懵逼,开方怎么打tag,打不了tag的话,分块就没啥用了啊,所以肯定是花式打tag的!,就不打tag,就直接暴力!那要分块干嘛呀,等等你就知道了嘿嘿嘿(滑稽)

    先分析一下数据哈:有负数!!!负数怎么开方,明显开不了啊,这个······

    还有哈,0和1也是,怎么开都一个样……

    最大的数据2^31-1,这个开方开5次就变成1了……

    这……,是不是分析完数据觉得这题很滑稽呀

    那么我们发现那些(<=1)的数修改起来简直是废的,除了耗时间什么用都没有。
    我们就可以从这方面入手啊。

    我们可以把那些小于等于1的数打标记,如果再搜到,不进行处理。那么一个一个打标记是几乎没有优化的,所以要一段一段打标记,所以就可以分块(怎么样,是不是完全想不到)

    (1).修改操作 修改呢,非整块就直接暴力对其进行开方,记得更新每块的和哦 QwQ /,而整块呢,我们下面讲整块操作。

    (2).查询操作 暴力对非整块的数累加,整块的就累加那一块的和。

    (3).整块修改操作 如果这一整块以前被打过标记,就退出,不然就暴力对块内数进行开方(记得更新每块的和),同时判断数是否小于等于1,如果都小于等于1就可以直接把这个块打标记了。

    下面就可以看代码了:#6281. 数列分块入门 5代码

    未完待续……

  • 相关阅读:
    Java JMX 监管
    Spring Boot REST(一)核心接口
    JSR 规范目录
    【平衡树】宠物收养所 HNOI 2004
    【树型DP】叶子的颜色 OUROJ 1698
    【匈牙利匹配】无题II HDU2236
    【贪心】Communication System POJ 1018
    【贪心】Moving Tables POJ 1083
    Calling Extraterrestrial Intelligence Again POJ 1411
    【贪心】Allowance POJ 3040
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678261.html
Copyright © 2011-2022 走看看