数据结构
分块
以前我对分块知之甚少,但其实分块中充分体现着人类智慧,用几道题记录一些常见 trick。
面试的考验[JSOI]
此题有双 log 离线做法,不说。
这题的分块做法我认为最智慧的地方就是,这些题总是有一些很显然的
按照我以前对分块的理解,分块应该是把一维的一坨东西分块,然后每个块里面与处理一些东西,询问的时候一个块一个块跳,再把边界的处理一下。 但是这题的做法为我开启了分块的大门,idea 来自题解。
分块还有一种常见预处理做法,就是预处理出每一个块到另一个块的连续段的贡献,由于组合最多
首先块内排序,这样我们就可以按照大小取出两边零碎的东西,这里的消 log 操作非常聪明! 于是统计这一部分的答案是严格
然后考虑先处理每个点到每一段块(是指从与这个点相邻的块一直连续到某一个块)的贡献,我们发现一共有
设我们现在要处理第
然后我们处理块到块的答案。我们可以用刚才处理出来的这个数组进行统计,这个次序也超级巧妙啊,这里我们只需要暴力更新即可。
Yuno loves sqrt technology III
这题有两种分块做法,不过 lxl 卡掉了一种常数小空间大的做法(可以在 P4168 中提交),虽然有了上题的经验,但我还是被这两种人类智慧惊艳到了。
还是要预处理每个块到每个块的众数(及其出现次数),然后我们就看两边零散的这些数能否通过零散出现超过块间众数的出现次数。
空间大的做法:前缀和! 为什么我没有想到呢。 处理出来每个数字在前几个块中的出现次数即可!空间
更智慧的做法:用一个 vector 记录下每一个值出现的各个位置,然后!我们暴力更新答案!因为边上的零散内容最多出现
类似题目:作诗。
游戏王
洛谷民间比赛的分治+分块好题。数论分块之类的思想,以前没遇到过,很巧妙!
首先看到这种乘积限制的题目就应该考虑到这种做法。我们会发现如果
// 加点题外话,去 wiki 上查了一下数论分块,有一个结论。对于一个数字
这个题的分治也非常巧妙。由于每次查询的是某一段区间的答案,加上我们已经有根号级的基数,暴力或回滚莫队之类的显然不行。于是就有了分治做法!分治后求出从分治中心向两边延伸的前缀和(后缀和),然后对于跨过中心的询问处理,算出把其对应的两半合并到
众数[ZJOI]
根号分治神题。
Wei 老师提示我们。看到出现次数应该考虑根号分治。这个题目要我们做的是在序列中选出一段区间,然后求这段区间的众数加上区间外众数的最大值。
按照出现次数根号分治。对于出现次数大于
然后考虑出现次数小的数之间的答案。这个实际上我们只需要考虑一种颜色在内或在外(其中一种)的情况即可,因为另一种情况会被别的颜色考虑到。这半部分我们需要考虑另一种做法了。我想的是计算在外面的答案,这样也好更新最终答案有哪些。我们把其它的颜色综合起来考虑,对于考虑的这个颜色,枚举左端点和右端点(复杂度是这个颜色出现次数的平方),然后查询这个区间里的众数就好了。
至于怎么处理区间众数,我们得考虑另一个性质。由于我们现在考虑的是小数之间的答案,所以这个众数也一定是在
最后这一半的复杂度是 每种颜色出现次数小于等于
实现起来非常麻烦,很好很好的题。贴下代码
我突然发现好像江浙还挺喜欢考分块的,这题评黑绝不过分
[HNOI2010] 弹飞绵羊
图论题居然也能分块。这是一个 lct 的板子,建图方式显然,但我不会 lct。分块很奇妙。分块后维护每个点第一次跳出这个块的位置即可,过于巧妙。
带修区间 mex
揉合了很多 trick,非常非常聪明的
分治
The Number of Subpermutations
好题。题解区有人在星战前夕预言异或哈希可能将流传开来。
这题难点不在哈希,原根哈希,异或哈希,或者各种乱搞哈希应该都能过。有很优雅的线性做法,但我重点想说的是分治做法(主要是我不会这种做法)。
考虑枚举区间的最大值。然后我们按照最大值分治,设这个最大值是
很巧妙的一种分治方法。
寒假作业
简单题,很经典的分治。但是我想了十多分钟。 首先
分治,然后计算分治中心左边的后缀和,排序,再枚举右边的前缀和,二分查找个数。
莫队
简单回顾一下板子。
普通莫队 注意加奇偶排序优化常数,证明过于简单。
带修莫队 按照前两维所在块排序,第三维从小到大即可。块长取
证明:设块长为
树上莫队 把树用欧拉序转成一维的东西,然后按普通莫队做即可,注意统计贡献时只统计出现次数为奇数次的即可。
回滚莫队 算是对普通莫队的优化,就是有一些题不方便加或删,每次强行把左端点所在块暴力处理即可,注意右端点的排序方式有对应要求,不能奇偶排序。
RMQ
经验的积累还是得靠刷题啊。
RMQ 有很多非常聪明的利用。
NOI2010超级钢琴
求最大的
线段树
线段树合并/分裂
线段树合并单次最坏
一些 trick
排序
学到了一个 trick,给一段区间排序可以转化为给一段 01 串排序。01 串排序用线段树维护可以做到
[NOIP2022] 比赛
NOIP2023 前夕,终于把这个题补了。扫描线是一个应用广泛的经典 trick。
先说一种部分分做法,感觉这个比较容易理解。没扫描线那么抽象。
记录下每个点之前第一个比它大的值的位置,可以用单调栈做到线性,记为
我们在区间
然后我们把这个问题转化成线段树维护历史版本和。
我们发现当我们从
这就相当于我们对于每一个询问
维护区间历史版本和的一种常规做法是开一个辅助数组。
对于普通区间加减的线段树,我们设一个节点的值是
另一种做法就是硬维护标记,记一下最后一次更新的时间,然后区间加的时候,记录一下标记的累计值,标记乘时间的累计值,每次更新的时候就让标记乘时间的累计值加上标记累计值乘时间差,然后标记累计值直接维护即可。标记不下传,直接永久化,询问的时候输出区间和乘时间加上标记乘时间的累计值。
对于这个题我们可以类似地处理。
两种方法都可做,我选用第二种。如果区间赋值
教训:ull 不能用memset初始化1,这一堆烂 tag 改了好几天,结果输出写成 lld 了一直在那输出
话说这个东西怎么抽象成扫描线的啊。
复杂度
Excluded Min
放点有水平的线段树。对答案扫描线的牛逼思想+一个牛逼 trick。
题意是给你一个序列,然后若干次询问,每次询问一个区间,问由这个区间的数组成的可重集合,经过若干次“操作”,能够达到的最大 mex。其中一次“操作”是指,把一个出现次数大于
容易发现判断一个可重集合能否达到一个 mex 的方法是判断在答案的值域内的数是否大于等于答案 -1。mex 这种东西不能二分,然后我第一次在某模拟赛做这个的时候只想到了一个
大致思路就是用莫队加线段树维护,用线段树维护以某个值为答案能否合法,可以简单用一个式子转换成这个点的值是否大于等于零来判断是否合法,每次区间加入或删除一个数只用在线段树上区间修改,然后在线段树上二分,找到最靠右的值大于等于 0 的位置,查询的复杂度是单 log 的,对于区间互不包含的百分之三十,可以把区间排序后做到
下面说正解,非常逆天。对答案扫描线,从上往下扫答案,判断有哪些区间可以满足当前答案。但是我们发现很难维护这些区间,这时就有一个非常牛逼的 trick。我们发现对于两个互相包含的区间,被包含的区间的答案一定小于等于包含它的区间的答案,于是我们先只提取出来不被包含的区间,等这个区间找到答案再把它包含的区间取出来。
这个想法非常的好,因为如果区间互不包含,那么可以排序使得这些区间的左右端点同时单调。你删除一个位置的元素的时候他就只会影响到一段区间。这时对提取出的区间维护答案就很容易了,每次把枚举到的答案的值对应的每个位置删掉就行了,用线段树维护。
然后考虑如何维护还没被加进去的区间,再用一个线段树维护,我们删除一个区间时,加入被它包含的区间的时候,我们需要注意什么呢?我们需要注意不漏(不会落下区间),并且不与已经取出的区间产生互相包含的关系,至于是否会重复取出就不用担心了,因为一个区间被取出后线段树里也会对应删除它。为了满足上述条件,我们需要维护左端点在某一个区间内,右端点最大的点,然后注意删除一个区间时,可能会取出来很多它包含的区间。
这时还有一个问题,就是新取出的区间的值(我们在第一个线段树里维护的值)是多少,由于它一开始没被取出来,我们需要重新计算这个值,这个也很容易,用另外一个树状数组,维护这个区间还有多少小于答案的点即可。
具体实现细节,比如找到第一个线段树要更新的区间之类的可以再开个 set 来辅助比较好写,最终复杂度是
PermutationForces
做完上面的题以后蔬菜推的。
首先考虑暴力,我一开始考虑的是二分答案,然后发现在答案确定的情况下,删点的顺序不影响结果,删点不会使情况变劣。于是我们发现不需要二分答案,每次选择删除代价最小的点删除即为最优,然后用当前的最小代价更新答案,证明很容易,简单想一下就好。
然后我们考虑如何找到当前代价最小的点,暴力是
我们发现每次删除一个点我们假设是
蔬菜提醒我和上一个题联系起来。
虽然是一个二维关系,但是实际点的个数只有
题解好像写的比较简单,我懒得看了,我这里就说一下我的巨屎分讨写法吧。
我把
于是又变成了上一题的情况,只不过刚好相反,这次维护的是被包含的区间,然后由于我们分了两类,需要用四棵线段树来维护答案和没被提取出来的点,然后用上面说的更新已经被提取出来的点,这个时候你发现有一个很好的地方,就是你实际删除一个点只会更新另一类区间的某一段的答案,不用再分讨一大坨了。
这时我又卡住了。我不会计算新提取出来的区间的答案了,我被禁锢在了用上面的方法来更新答案的形式了。后来想了想,发现和上一题一样,新提取出来的区间可以直接换一个角度计算答案,我们只需用两个树状数组记录被删除的点的
写了 5.3k,单 log 的做法跑了两秒,常数巨大。
[博弈论]()
外培的时候做的原创题,不挂链接了。蔬菜又场切了。
题意给一个
首先需要想到扫描线。这里说从上往下扫的思路,(从左往右也可以扫)。然后考虑如何统计贡献,假设到当前这一行第
然后考虑怎么维护这个东西,我们发现我们需要维护的操作只有区间翻转,而区间翻转后,原来是
我们发现随着扫描线往下扫,没被翻转到的区间,原来非
由于每一个时间戳上的
然后考虑维护贡献的数据结构,答案会发生什么变化,每次从中删除若干个值为
然后注意每次多扫一行,更新整体增加的答案(没被翻转的地方也会增加答案)。总计,需要两个树状数组,一个珂朵莉树,一个线段树,复杂度单 log。
主席树
翻到了之前做过的题,发现做过的都忘了。
神秘数(突然想起来以前还在这题里跟人对线过,不过要不是看到那个帖子也不会发现这么好的题)
考虑暴力。对于一段区间
怎么优化。假设当前的答案是
Rollbacks
我们需要快速地切换版本和更新版本,于是用主席树维护。
但是这个题维护版本也挺麻烦的,因为既有添加又有删除。但有一个突破口,就是撤回操作不可撤回。也就是说一旦发生撤回操作,它撤回之的这一步以后再也不用管了。
于是考虑维护相对位置,我们发现不管你对后续做什么操作,当你撤回到某一个操作的时候,这个序列都是一定的。它前面的第几个是谁就是谁,其实就是个树,它的祖先不会变,但是兄弟和子孙会变。
因此考虑倍增维护祖先。
然后 MLE,空间占用 320M。为啥要限制到 250M 啊。
翻题解。发现有高妙做法。把主席树底层 30 个位置一压,好像是叫底层分块。然后我发现还过不了,270M。。
于是求助蔬菜,然后它提供了一种更聪明的做法。可以用 vector 替代掉倍增数组,再套上我 180M 的底层分块主席树,刚刚好。
这题其实有优雅
浅谈势能分析
今天看了 splay 的势能分析。看了很久还在群友的帮助下才理解。没有深刻掌握,这里只是简单的记录一下我的理解。
对于一些不好直接分析复杂度的做法,采用放缩的形式,把若干次操作放缩成势能的变化,就是设计一个势能函数,使得能够把每次操作放缩成相邻两个状态的势能差,然后就能使多次操作放缩成始末状态的势能差。
例如 splay 的势能分析,它甚至为了构造成两个状态的势能差,还把复杂度凭空又加了一个势能值,用加上势能值的上界减去势能本身的下界得到真正的上界,很神奇。
真的不知道人类是如何想出来的。