Sqrt Technology(长期更新)

Yuno loves sqrt technology!

分块

分块是一种优雅的暴力,巧妙地平衡了时间复杂度。

基本思想是对每一块预处理,查询/修改时对散块进行适当的暴力,使得复杂度正确。

时间复杂度取决于块长,一般可以用均值不等式求出理论最优块长。但是理论最优不等同于实际最优,根据实际情况调试。

基础部分

区间加,区间和

对每块维护sum,tag

区间加:对散块暴力修改aisum,对整块维护tagsum

区间和:对散块暴力将aitag加起来,对整块直接加上sum

时间复杂度:设总长度为nm次操作,(m,n同阶)块长为B,则一共有nB个块,一次操作时间复杂度O(B+nB),当B=n时最优,O(nn)

区间加,区间乘,区间和

对每块维护sum,add,mul

区间加:同上。

区间乘:对散块先下放已有的标记(暴力修改一遍),再对需要修改的暴力修,对整块维护sum,add,mul标记(都要乘)。

区间和:对散块中的元素用add,mul计算出实际值,暴力加起来,对整块直接加上sum

时间复杂度O(nn)

区间加,区间查小于x的值的个数

对每块维护tag和排序后的数列。

区间加:对散块暴力修改,改完后重新排序(重构),对整块维护tag

区间查:对散块直接暴力数(记得考虑tag),对整块在块内二分一下(记得考虑tag)。

时间复杂度O(nnlogn)

区间加,区间查x的前驱

要维护的东西同上。

区间加同上。

区间查:散块中暴力找小于x的数中最大的,整块中二分找,求max即可。

时间复杂度O(nnlogn)

区间开方,区间求和

对每块维护是否全为0/1tagsum

区间开方:观察到在进行若干次开方操作后一段区会都变成0/1,且开方操作下一个数最多5次就会变成0/1,于是散块和没有tag的块暴力修改即可,跳过有tag的块,改完后维护tag,sum

区间求和:散块暴力加,整块加sum

时间复杂度:暴力修改的次数其实很少,对一个数暴力开方不会超过5次,所以m次区间开方的总复杂度可以视作O(n)的,于是时间复杂度取决于区间求和以及块长,直接取块长B=n,时间复杂度约为O(nn)

单点插入,单点询问

块状链表,将原数列串成链表再分块,设块长阈值为B

单点插入:直接插入。若某块块长2B,就分裂成两个块,若某块块长12B,与相邻块合并。

感觉不太好写,提供另一种想法:

定期重构一下,每插入n次就重构,一共重构n次,每次O(n),于是重构的总复杂度为O(nn),是正确的。

单点查询:先跳整块直到在同一块中,再一个一个跳。

时间复杂度O(nn)

区间赋值,区间查等于x的数的个数

维护每个块是否纯色以及是哪种颜色。

区间赋值:散块暴力下放标记再赋值,整块直接维护标记。

区间查:散块暴力查,纯色块直接查,杂色块直接暴力。

看上去太暴力了,但是经过一通分析后是O(nn)但我分析不太明白

可以观察到一次修改最多使首尾两个散块所在的块从纯色变为杂色,于是均摊下来一次还是O(n)的。

区间查最小众数

预处理fi,j表示从第i块到第j块的众数,gi,j表示fi,j的出现次数。(话说gi,j可以用下文的cnt算出来啊QwQ)。

于是可以观察到,一段区间的最小众数只可能是整块区间中的众数或者散块中出现的数。

于是再预处理cnti,j表示前i块中j的出现次数。

查询时枚举散块中的每种数,数出其在散块中的出现次数,再进行比较即可。

散块中数的个数是n级别的,预处理O(nn),块长取n,于是总复杂度O(nn)

另一种解法:

求一个区间内x的出现次数,可以先用个vector存x出现的下标,将区间左右端点在vector中二分一下即可。块长要取nlogn,有点慢,可以借鉴思路。

Ynoi大分块

我妻由乃太可爱了!

虽然叫分块,但是后面的题目还是需要莫队的知识啊

怀疑有生之年能补完吗......

最初分块

望月悲叹的最初分块。

最初分块:未来日记

维护序列a,支持以下两种操作:

  • 将区间[l,r]中所有的x修改为y

  • 查询区间[l,r]中的第k小。

Sol:

第二分块

突刺贯穿的第二分块。

第二分块:五彩斑斓的世界

维护序列a,支持以下两种操作:

  • 将区间[l,r]中所有大于x的数减去x

  • 查询区间[l,r]x的出现次数。

Sol:

第四分块

拭尽破净的第四分块。

第六分块

深潜循藏的第六分块。

第七分块

????的第七分块。

第十分块

????的第十分块。

第十一分块

沉滞留驻的第十一分块。

第十三分块

????的第十三分块。

第十四分块

点缀光辉的第十四分块。

莫队

对于序列上的区间询问问题,如果从[l,r]O(k)地扩展到[l+1,r],[l1,r],[l,r+1],[l,r1],那么可以O(knm)回答所有询问。

具体而言,将询问离线并排序,通过暴力移动当前答案区间[l,r]来回答询问。其复杂度由分块和排序方式保证。

小寄巧:写完add之后,可以从下到上扫描你的add,然后从上到下在你的del函数中执行相反的操作。

可以理解成adddel的对称性很高,但是别全用这个寄巧,能想明白细节还是尽量想,别寄了。

莫队擅长于解决单点对于点集的贡献,通常贡献形式为点对,于是考虑问题的时候要抽象成点对的形式,利用一下前缀和/差分之类的。

普通莫队

不带修,区间转移都为O(1)

排序方式:左端点所在块的编号升序,右端点升序。

还可以优化:奇偶化排序。左端点所在块的编号升序,当左端点所在块的编号为奇数时,右端点升序,反之右端点降序。

设置块长为nm最优。n,m同阶时,取n

时间复杂度O(nm)

注意移动区间时要先扩大再缩小,以防出现神秘错误。

带修莫队

强行给普通莫队加上一维时间维,就可以修改了。

也要保证时间维上的移动是O(1)的。

时间维上的移动,要考虑是加上修改还是撤销修改,并且要考虑对当前答案的影响。

块长一般取n23

排序方式:第一关键字:左端点所在块升序;第二关键字:右端点所在块升序;第三关键字:时间升序。

时间复杂度O(n53)

树上莫队

回滚莫队

如果在莫队的过程中增加/删除两种操作之一不能实现(或复杂度不正确),就考虑回滚莫队。

回滚莫队不实现难做的操作,而是用撤销影响的方式代替。

不删除莫队

具体操作如下:

  1. 将询问按左端点所在块升序,左端点同一个块内则右端点升序排序。

  2. 如果询问的左右端点在同一个块内,特判,直接暴力回答。

  3. 如果处理到了下一个块,那么清空莫队(撤销影响而非删除),并令莫队的左端点移动到新块的右端点加一处,右端点移动到新块的右端点。并且要清空原有的答案。

  4. 左端点在同一个块内时,右端点是单调不降的,可以直接移动右端点,不必回滚。

  5. 而左端点是乱序的,每次移动左端点回答询问后都令左端点回滚到起点,并且恢复原来的答案(具体实现时可以不修改原有答案,用tmp保存原有答案后对tmp进行修改,回答询问后把tmp扔了就行)(还是撤销影响而非删除)。

这样就只有撤销和增加的操作,避免了删除。

分析一下时间复杂度(认为n,m同阶):

  • 暴力部分,一次最多O(n),总共O(nn)

  • 清空莫队操作,一次O(n),最多有n个块,执行n次,总共O(nn)

  • 移动右端点,同一个块内移动右端点最多O(n),总共n个块,总共O(nn)

  • 移动左端点,一次询问最多O(n),总共O(nn)

综上,时间复杂度O(nn)

不增加莫队

类比上文:

  1. 将询问按左端点所在块升序,左端点同一个块内则右端点降序排序.

  2. 左右端点同一个块内不需要暴力,但是也可以暴力回答。

  3. 处理到新块就将左端点设为新块的左端点,右端点移动到序列末尾。(这里要求能够在正确的时间复杂度里进行初始化答案)。

  4. 同一个块内右端点单调不增,直接移动。

  5. 左端点乱序,移动后进行回滚。

复杂度同上文分析,O(nn)

莫队二次离线

当增加和删除的转移复杂度都很高时,考虑二次离线。

我们可以将莫队移动的这些端点同样离线下来计算。前后离线两次,故称二次离线。

具体流程如下:

  1. 先跑一遍莫队,增/删一个点产生的贡献拆成前缀相减的形式(这里要求贡献具有可减性,可以进行差分)。且形式很特殊,下面进行讨论:

f(x,[l,r])表示位置x对区间[l,r]产生的贡献。

[l,r][l,r+1]f(r+1,[l,r])=f(r+1,[1,r])f(r+1,[1,l1])

[l,r][l,r1]f(r,[l,r1])=f(r,[1,r1])+f(r,[1,l1])

[l,r][l+1,r]f(l,[l,r])=f(l,[1,r])+f(l,[1,l])

[l,r][l1,r]f(l1,[l,r])=f(l1,[1,r])f(l1,[1,l1])

真是酣畅淋漓的讨论啊。

  1. 找到发明一种数据结构,使得可以O(1)或稍劣的复杂度查询点对点集的贡献,O(n)或稍劣修改点集。

  2. 先预处理,扫一遍求出形如f(i,[1,i1])以及f(i,[1,i])的贡献,显然用找到的数据结构直接扫一遍是O(nn)的。

  3. 跑一遍莫队,把要求的形如f(x,[1,l1])x记录下来,挂在前缀[1,l1]上。注意到这些点x是一段连续区间,储存时直接存区间即可。注意到产生的区间数是O(n)的,空间复杂度从O(nn)优化到了O(n)。总点数因为在跑莫队进行nn次移动,是nn的。

  4. 最后在扫一遍前缀,对每个前缀处理它产生的贡献。由于一共n个前缀,每个前缀修改一次,这部分O(nn)。一共产生了nn个询问点,单次查询O(1),这部分也是O(nn)的。所以总体复杂度O(nn)

找到的数据结构很可能是分块,也可能是暴力加人类智慧。

修改和查询的复杂度可以稍劣,按照以上复杂度分析下来是对的就行。

特别注意: f(l,[1,l])f(l1,[1,l1])这种自己对自己贡献的东西,考虑是否会算重,是否要算,注意到f(l,[1,r])中同样有自己对自己的贡献。小心考虑。

一些经典问题

区间颜色种数

典,直接上普通莫队,维护每种颜色出现次数,次数从1减到0或者从0加到1时增减颜色种数即可。

区间颜色出现次数与排名之积

这是一次比赛题(2024.11.25 T2)的简化题意:

询问一个区间[l,r]中,最小化1kn(k1)lir[ai=colk]的值,其中colk是自己选择的一个排列。

a数组长为nm次询问。n,m2×105

看到数据范围猜测莫队。然后看到神秘的区间询问更是莫队了。

式子的后面那一坨其实就是区间内这种颜色的出现次数。

首先是上排序不等式,发现逆序乘上去可以最小化(就是大的出现次数和尽量小的(k1)相乘)。然后就是维护一个类似出现次数乘上次数的排名这样的东西。

考虑莫队移动端点时会如何变化。发现考虑次数的排名会遇到“次数的出现次数”的这种概念。

但是先别急,考虑莫队增加一个点的变化,设这个点的颜色为p,颜色p已经出现了k次,加上这个点后这个点的颜色出现了k+1次。设在加入这个点之前,原本出现了k+1次及以上次数的颜色种类数有s种。那么这s种对应乘上了0s1。那么颜色p原本的贡献为s×k,现在新加一个点后贡献为s×(k+1),所以贡献增加了s

然后考虑莫队减少一个点的变化,同上设,颜色p已出现k次,减去后出现k1次,出现次数为k次及以上次数的颜色种类数有s种。于是颜色p原本的贡献为(s1)k,减去一个点后贡献为(s1)×(k1),于是贡献减少了s1

思考一下要是有一些颜色的出现次数相同怎么办?贡献似乎不能说就是s×k或者是(s1)×k。但实际上是没有影响的。出现次数相同意味着对答案的贡献只有前面排名处乘上的系数不同。若是颜色p此刻要发生变化,可以认为将p移动到了对答案的贡献序列中同种出现次数的首/尾,即钦定了它的贡献是s×k或者(s1)×k。那么就不必再多想了。

转移就可以做到O(1)了,维护一下出现次数在k次及以上次数的颜色种类数,以及每种颜色的出现次数,然后修改的时候只用改一下相邻的就好。

总的还是普通莫队,O(nm)

根号分治

我们可以设置阈值B,发现题目中一个东西B时暴力可做,>B时有另一个东西B,对另一个东西暴力也可做,于是这道题就可以做了。

看看题。

posted @   RandomShuffle  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示