分块
根号算法有很多。分块是其中一种。
分块是一种奇妙的思想。
简单地说,就是对一个序列分为若干块,每一块之多有B个元素。
很多传统的分块题,在大型比赛中已经不玩了,所以,这些传统题最多练一下代码能力,以及分类讨论的能力。
HOW TO PLAY
时间复杂度是一个关于B的式子,通过调整B的大小,能使时间复杂度最优。
如果有闲情,将修改的时间复杂度和查询的时间复杂度列一下,感觉会好些。
接下来,那就分享一下学到了什么。
对树状数组或线段树操作依赖性较强的,请克服一下。
对于带log的操作:能用树状数组的,就不要用线段树。
分块讲师道,这类知识最好结合实例来。不要空想。
基本操作
第一种基本操作:询问区间分整块和散块两部分。
例题1
JZOJ的一道题目。
称一个数是特殊的,当且仅当这个数只含有4和7两种数字。
给一个序列a,支持两种操作。①将[l,r]区间的a[i]+=v。②询问[l,r]有多少个特殊的数。
在整个过程中,a[i]始终<=10000.
设sum[i][j]表示第i个块中j的个数。区间加,整块的,就打标记;散块的,暴力修改。
修改操作:O(n)O(\sqrt{n})O(n)
询问操作:O(n)O(\sqrt{n})O(n)
第二种基本操作:修改的时候暴力重构块,询问的时候枚举这个区间包含的块。
结合一道例题。
例题2
BZOJ2002
有n个点,每个点有一个a[i],表示从i可以跳到i+a[i]。问从u点跳几步能够跳出n(即>n)
n<=200000,m<=100000
对于每个点i,维护i点跳多少步跳出所在块。
以及它跳到下一块的哪个点。
修改操作:O(n)O(\sqrt{n})O(n)
询问操作:O(n)O(\sqrt{n})O(n)
稍微高级一点
无修改操作
运用第一种基本思想,我们希望整块的信息能够尽快求出。比如说用O(1)的时间求出。
O(1)可以直接查询数组里的值,也可以差分。
可以使用预处理。(在询问的时候不方便直接求整块的信息)
例题3
BZOJ4241
求一个区间的【一个数的出现次数*该数】的最大值。
设ans[i][j]ans[i][j]ans[i][j]表示第i块到第j块的答案。
sum[i][j]sum[i][j]sum[i][j]表示前i块中元素j的出现次数。
对于整块的,直接查询ans[i][j]ans[i][j]ans[i][j];对于散块的,利用sum,O(n)O(\sqrt{n})O(n)更新答案。
预处理操作:O(n)O(\sqrt{n})O(n)
询问操作:O(n)O(\sqrt{n})O(n)
在碰到一道新的题目时,需要预处理什么?预处理那些能够将整块的信息尽快求出的值。
带修改操作
暴力重构块
也就是补充一下第二种基础操作。
遇到一个修改操作,比如区间修改。如果跨过整块,那么打个标记;对于散块的,那么就暴力重构该块。
有这么些该注意的地方:
每一次修改,维护所存储的信息,以至可以查询。这个过程的时间复杂度不宜过大。
例题4
JZOJ 5871(60分)
给一个序列,前缀和用s[i]表示,求使得a[i]=s[i−1]a[i]=s[i-1]a[i]=s[i−1]的最小的i,若没有,输出-1.
支持在线修改操作,共m个操作。
n,m<=50000.
分成O(n)O(\sqrt{n})O(n)块,令i所在的块的最左边的元素为L,b[i]b[i]b[i]表示a[i]−(s[i]−s[L−1])a[i]-(s[i]-s[L-1])a[i]−(s[i]−s[L−1])
每次修改,直接暴力重构块,询问的话,一块一块扫,设目前的块为i,前面的块的前缀和为si−1s_{i-1}si−1。如果找到一个位置k,使得b[k]=si−1b[k]=s_{i-1}b[k]=si−1,那么答案即为k。
修改操作:O(n)O(\sqrt{n})O(n)
询问操作:O(n)O(\sqrt{n})O(n)
例题5
BZOJ3787 Gty的文艺妹子序列
支持在线修改,询问逆序对。
解题思路
需要维护什么?使得修改的时侯枚举的复杂度为O(n)O(n)O(n)?
在线修改查询,什么数据结构可以做到一个log?
ans[i][j]ans[i][j]ans[i][j]表示从第i个块,第j个块各选出一个元素所造成的逆序对的个数。
sum[i][j]sum[i][j]sum[i][j]表示前i个块中元素j的出现次数。
ans,sum均可视为有O(n)O(\sqrt{n})O(n)个树状数组,维护的是前缀和。
逆序对的贡献可以分为三部分,一个是跨整块的,一个是块内的,一个是散块的。
设num[i]num[i]num[i]表示第i块的逆序对数。
处理跨整块的:O(n∗log(n))O(\sqrt{n}*log(n))O(n∗log(n))
处理散块的:O(n∗log(n))O(\sqrt{n}*log(n))O(n∗log(n))
处理块内的:O(n)O(\sqrt{n})O(n)
很多的涉及维护元素值的题目中,若用分块维护不了,那么很难用分块做。
比如查询元素的值在[l,r][l,r][l,r]的个数之类的。(题做多就好了)
区间修改,区间查询逆序对我并不会做。
注意:带修改操作的问题,不一定都能用分块解决。具体问题具体分析。
对值域分块
条件:n与a[i]同阶,或者值域的范围很小
这只是一种思想。
就是在维护信息的时候,专门有一维维护第i块值域的元素的信息。
例题6
求区间的k小。特别地,如果一个区间内a[i]出现的次数>m,则a[i]=n。
所有的a[i]都<=n。
主席树貌似处理不了a[i]出现的次数>m,则a[i]=n的情况。
分块。
设ans[i][j][k]ans[i][j][k]ans[i][j][k]表示第i块到第j块,值域为第k块的出现次数<=m的数的个数。
sum[i][j]sum[i][j]sum[i][j]表示前i块中,数j的出现次数。
询问:整块的,O(n)O(\sqrt{n})O(n)暴力就好了;散块的,拿一个数组维护一下。O(n)O(\sqrt{n})O(n)
所以,询问的复杂度:O(n)O(\sqrt{n})O(n)。
其他思想
平衡结合
其实在HOW TO PLAY一栏中已经有所提及。
设块大小为B,计算出每一种操作的时间复杂度,从而得出B的最优值。
均值法
均值法,就是让各部分的时间复杂度均衡,从而达到时间复杂度最优的算法。
例题7
一个序列aaa,每次询问b,cb,cb,c,对于所有的i≡c(mod k)i≡c(mod\ k)i≡c(mod k),求∑a[i]\sum a[i]∑a[i]
询问个数mmm与nnn同阶。
无修改。(有修改?)
解题思路
解决这题的暴力方法有很多。
方法①,设f[b][c]f[b][c]f[b][c]表示b,cb,cb,c的答案,预处理。
方法②,不预处理,固定b,c,直接暴力计算。
方法①的时间复杂度为O(n∗b)O(n*b)O(n∗b)
方法②的时间复杂度为O(nb∗m1)O(\frac{n}{b}*m_1)O(bn∗m1),m1m_1m1代表用方法②计算答案的询问个数。
运用均值法,总时间复杂度为O(nb+n∗nb)O(nb+\frac{n*n}{b})O(nb+bn∗n).
显然,当b=nb=\sqrt{n}b=n时,总时间复杂度最优,是O(nn)O(n\sqrt{n})O(nn)
难点
去掉log
我对log算法依赖性强。所以去掉log有一点难度。
例题8
BZOJ3744 Gty的妹子序列
在线查询区间[l,r][l,r][l,r]的逆序对数。
O(n1.5log n)O(n^{1.5}log\ n)O(n1.5log n)很好做,随便用树状数组维护一下就好了。
一句废话:树状数组支持的是单点查询,区间修改。
去掉log,那么要分很多种情况讨论。
具体的题解见我的其他博客。
难分难解(回滚莫队)
难以支持删除
不会。
难以支持插入
不会。
知识串联
分块算法里,学得是思想。
①分块篇
A:首先需要将块的大小BBB设置好,时间复杂度是一个关于BBB的式子,通过调整BBB来进行时间复杂度的优化。
B:分了块后干嘛?分析每一部分的时间复杂度。
②维护信息篇
A:维护整块的信息。暴力重构;设的数组使得,涉及的被修改的元素不多【详见例题5】;块状链表?(不会啊)
B:维护散块。一般就要利用到某些预处理的东西了,(这个预处理的信息可以支持暴力修改) 【详见例题3】。
③分类讨论篇
A:找出所有的情况,别觉得情况多就放弃。
B:整块的,散块的,块内的,跨整块与散块的,跨整块与整块的等等,将所有情况找到合理的解决方案。某种情况如果很难求出答案,那么可以照葫芦画瓢(即套用求其他情况的答案的方法,或综合一下)【详见BZOJ 3744】
④时间复杂度篇
所谓的分析每一部分的时间复杂度,其实就是将时间复杂度写出来,然后通过想到的优化或者调整块的大小,从而优化时间复杂度【详见BZOJ 4867】
后记
学的东西毕竟太少,如果找到关于分块算法这一块的题目,希望大家能够和我交流。