CDQZ DS 题单总结(上)
Preview:
个人认为是一套非常好的题单,能在各个方面练习 DS 水平,并且很多题型也是比赛当中的经典题
Challenge 0:
简单的数组,懒得写了。
Challenge 1:
考虑每一次修改所带来的的影响,因为修改是直接对上一个版本而不是任意一个版本进行修改,所以我们可以让每一个位置维护一个 vector,当我们修改某个位置时,就把操作序号和修改的值 push_back 进 vector 里面,然后查询第几次操作时某位置的值就变成了在 vector 上面二分。时间复杂度 \(O(M \log |opt|)\)
Challenge 2:
主席树,但是大型动态开点现场。
我们对于操作二,可以理解为在当前最新版本查询第 \(x\) 个元素。
对于操作三,我们可以理解为 copy 相对于第 \(x\) 前版本,使之成为最新版本。
至于操作一,我们可以预先分配好 \(1e5\) 长度的序列,然后加减数就是动态开点式添点,因为我们的加是直接在尾部添加,所以我们每个叶子结点只需要两个信息:当前值和元素个数,然后 \(\text{pushup}\) 元素个数就行了。
Challenge 3:
线段树板子题,懒得记录了。
Challenge 4:
有两种思路,先说第一种:
对于操作 \(2\),我们可以观察一下,发现其本质是区间和的平方减去区间平方和再除以 \(2\),所以让线段树维护区间和和区间平方和就可以了。
至于操作 \(3\) 嘛,因为只是单点修改,所以怎么乱搞都可以过。
第二种是机房 \(\text{gxd}\) 大神提出来的(膜拜大神 \(\text{gxd}\)):
对于一个区间,我们维护两个值,分别是两两相乘的值和区间和,对于区间的合并,我们则有:
然后就做完了,毕竟是单点改,所以修改操作也是 \(O(M \log n)\) 的。
以上两种的时间复杂度都是 \(O(n \log n)\)
Challenge 5:
也是线段树板子题,不过 \(\text{lazytag}\) 下传的时候要考虑一下 \(\text{lazytag}\) 是否真的存在。
或许珂朵莉树也是个不错的选择?
Challenge 6:
(前言:楼房重建的翻版)
喵喵题,所有的喵喵之处全在 \(\text{pushup}\) 上了,其余的和板子一样。
对于本题,\(\text{pushup}\) 应该依照一下逻辑:
- 若当前区间的最大值小于 \(cmax\),则直接返回 \(0\)
- 若当前区间为叶子,则返回 \(cmax < seg[p].max\)
- 若当前区间的第一个元素大于 \(cmax\),则直接返回该区间的贡献。
- 若当前区间的左儿子的最大值小于等于 \(cmax\),则递归到右儿子
- (重点)若当前区间的左儿子最大值大于 \(cmax\),则递归到左儿子,此时右儿子的贡献则为 \(pushup(cmax,p<<1)+seg[p].data - seg[p<<1].data\)。
可能第五点不大好理解,这里详细说一下:
因为右儿子中有贡献的一定大于左儿子的最大值,所以我们无论左儿子中哪些数做出了贡献,右儿子那些有贡献的数必定已然拥有贡献,而且我们保证,若左儿子拥有贡献,则提供贡献的肯定包含左儿子的最大值,因为右儿子有贡献的必定大于左儿子的最大值,所以易证右儿子的贡献可以全部加上。
然后就是一个线段树单点修改的板子。
Challenge 7:
有两种思路,我们先说第一种:
用 \(\text{unordered\_map}\) 开个桶,然后顺便记录一下原序列,则此时更改序列某一个元素就变成了将桶中原来那个数的位置 \(-1\),修改后的数的位置 \(+1\)。
查询直接 \(\text{unordered\_map}\) 映射过来就好。
操作的时间复杂度近似于 \(O(1)\),但是不保证毒瘤出题人是否会卡你 \(\text{unordered\_map}\),所以换成 \(\text{map}\) 的话更稳妥但此时复杂度上升至了 \(O(\log n)\).
第二种思路和第一种一样,但是鉴于原题建议 \(\text{C++}\) 选手不要用 \(\text{STL}\),则我们可以用平衡树或者值域线段树来代替 STL。
时间复杂度 \(O(M \log n)\)
Challenge 8:
刚开始被唬了一跳,然后立马调整过来了。
一道非常不错的 “贪心+平衡树+堆+哈希表”
我们分析一下,当一个数插入进来的时候,在当前数列中与哪些数之间的差特别小,显然,肯定是与他的前驱或者后继相差最小。
于是维护一颗平衡树(实际上我用的是 \(\text{set}\),因为感觉自己写的容易 T 飞 ),他的作用主要是来查询当前待插入值得前驱后继。
那么对于差,我们维护一个小根堆(\(\text{priority\_queue}\)),此时显然堆顶就是我们想要的答案。
那么删除操作该怎么办呢,因为优先队列无法做到精准定位删除,所以我们可以让关于优先队列的删除操作在查询时完成,为了标记我们需要删除的值,我们还应当维护一张哈希表(\(\text{unordered\_map}\)),用来记录有哪些差值需要被删除多少次。
注意:
- 当我们删除一个值的时候,应当将他的前驱和后继的差再 \(\text{push}\) 进去,否则会少信息。
- 当我们插入一个值得时候,应当先找前驱后继再插入,否则会增加代码量或者有一定概率 G 掉
时间复杂度应该是个摊还分析,不会,盲猜 \(O(n \log n)\)
关于删除操作还有另外两种思路(全部来自于 \(\text{do\_while\_true}\),在这里万分感谢他):
- 使用 \(\text{multi\_set}\),作用和优先队列差不多(抛开对比优先队列的常数),但是支持迭代器 \(\text{erase}\),删除的时候直接 \(\text{lower\_bound} + \text{erase}\),查询最小只需要 \(\text{begin}\) 就行了。时间复杂度依旧是 \(O(M \log n)\)
- 可删堆的思路,似乎常数更小?均摊下来依旧是 \(O(M\log n)\) 
Challenge 9:
一道非常经典的 trick 好题
我们将每个线段树节点维护两个信息,分别是当前位置的数字和当前区间的有效元素个数,此时删除操作就变成了减少某个位置的有效元素个数,查询操作就变成了用当前的有效元素个数信息来凑出第 \(k\) 个数。
Challenge 10:
值域线段树或平衡树,原理同 Challenge 9。
注意查询右子树的时候应该是 \(k - \text{左子树的cnt}\)
Challenge 11:
主席树板子(雾),没啥技术含量(大雾),只要对线段树足够了解可以直接现魔改。
Challenge 12:
主席树板子二……
拓展:如果带单点修改怎么办
即答:主席树套树状数组

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号