我貌似和全部的数据结构都有些误会。。。。。。算法
在处理一些修改查询问题的时候,咱们能够利用分治的思想,好比说把一个线性的数据不断分红一棵二叉树,也就是咱们所说的线段树,这样咱们就能够在logn的时限里作到修改和查询。同理咱们也能够把数据分红一个只有两层的树(算上根节点三层),每一个节点分红sqrt(该节点大小)个节点,这就是咱们所说的分块了。数组
这里咱们主要讲解分块的思想:数据结构
话很少说先上一张图:3d
如图,咱们这就是一个分好块的结构。blog
那么,这个结构有个什么用呢?很明显,咱们能够用它维护单点修改与查询,区间的修改,查询。。。排序
看上去有些眼熟,是否是,想起来了些什么?模板
没错,就是线段树那几个操做。那么如今咱们继续,能够发现分块的修改和查询操做都是O(sqrt(n))的。那还要分块有个什么用??变量
咱们先来看这么一道题:重构
咱们刚开始看的时候可能以为能够用数据结构实现,什么线段树主席树平衡树均可以想想,不过一会你就会发现,它们都不可行,过了一会,你会发现有一种线段树套平衡树的方法或许可作,只不过代码复杂度很高,并且估计要调不短的时间吧。这个时候就是分块出场的时候了。二叉树
为何这个题不能经过线段树实现,缘由就在于第二个操做过于烦人,线段树维护的区间信息没法找出单点的,若是要查找一个区间中的全部点,没法获得一个让人满意的复杂度。那么为何分块就能够使人满意了呢??
首先咱们说明几个接下来常常会说的名词:
整块:查询范围内彻底包括的块,也就是范围内第二层的块。
零散块:查询范围内除了整块其余的部分,是第三层的块,也就是一个一个的点。
接着声明几个变量:
那么咱们再修改的时候,对于区间内全部的整块,咱们O(1)的打上加法标记便可。
那么对于全部的零散块呢?
咱们发现,咱们最多有两个部分的零散块,其中每一个部分里最多有不超过sqrt(n)个点,咱们直接暴力枚举下标修改便可,那么整个修改操做的复杂度就是O(sqrt(n));
那么咱们再查询的时候,对于每一个整块,咱们能够知道它是彻底有序的,因此咱们能够直接二分查找返回。对于零散块,咱们仍然选择暴力枚举,那么查询操做的复杂度就是O(sqrt(nlogn))。
因此整个操做咱们就能够在O(qsqrt(nlogn))的复杂度内完成了。
笔者秉承懂了算法不看模板的法则,本身手动实现了一下这个题目。接下来分块讲述一下代码的意义:
这一部分就是预处理而且分块的部分,和块的左右区间的处理。
而后在你的分块主体开始以前,必需要有的一条语句:
这条语句就是先把b数组的每个块中的数据排好序,这样要否则颇有可能找的时候因为每一块的b数组尚未排序致使查找错误。
而后就是分块的主体部分,咱们分红修改和查询分别看一下:
这个就是修改操做。要注意的是,在读入的x和y点属于同一个块的时候咱们要特殊操做。
若是不在同一个块里,咱们就遍历包含的全部整块,打上标记。而后对于左右那两个零散块,咱们直接枚举就能够了。为何用的是a数组而不是b数组?由于b数组在排完序以后下标表明的意义是数的大小顺序,而不是咱们一开始读入的顺序,不能直接修改b数组。还有一个就是,这里笔者为了打上去方便,修改b数组的时候用了sort,这样咱们的复杂度就增长了一个sqrt(logn)可是咱们使用归并重构零散块的话仍是能够达到sqrt(n)的。对于 在同一个块里的,咱们也是直接暴力枚举就能够了。
为何这些零散块能够暴力枚举,复杂度不会很高么?不会的,在修改每个零散块的时候,咱们最多也就修改sqrt(n)个点,因此并不会有特别高的额复杂度。
这个是查询操做,怎么查找刚才都已经说过了,一样零散块咱们就暴力枚举便可。
那么这道题就这么完成了。
纯粹让你用分块完成的题目比较少,可是咱们能够用这东西水一水分,毕竟这玩意比线段树什么的好写多了。