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}\)):

对于一个区间,我们维护两个值,分别是两两相乘的值和区间和,对于区间的合并,我们则有:

\[\text{合并出来的区间 = 原先两个区间的两两相乘值 + 两个区间的区间和相乘} \]

然后就做完了,毕竟是单点改,所以修改操作也是 \(O(M \log n)\) 的。

以上两种的时间复杂度都是 \(O(n \log n)\)

Challenge 5:

也是线段树板子题,不过 \(\text{lazytag}\) 下传的时候要考虑一下 \(\text{lazytag}\) 是否真的存在。

或许珂朵莉树也是个不错的选择?

Challenge 6:

(前言:楼房重建的翻版)

喵喵题,所有的喵喵之处全在 \(\text{pushup}\) 上了,其余的和板子一样。

对于本题,\(\text{pushup}\) 应该依照一下逻辑:

  1. 若当前区间的最大值小于 \(cmax\),则直接返回 \(0\)
  2. 若当前区间为叶子,则返回 \(cmax < seg[p].max\)
  3. 若当前区间的第一个元素大于 \(cmax\),则直接返回该区间的贡献。
  4. 若当前区间的左儿子的最大值小于等于 \(cmax\),则递归到右儿子
  5. (重点)若当前区间的左儿子最大值大于 \(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}\)),用来记录有哪些差值需要被删除多少次。

注意:

  1. 当我们删除一个值的时候,应当将他的前驱和后继的差再 \(\text{push}\) 进去,否则会少信息。
  2. 当我们插入一个值得时候,应当先找前驱后继再插入,否则会增加代码量或者有一定概率 G 掉

时间复杂度应该是个摊还分析,不会,盲猜 \(O(n \log n)\)

关于删除操作还有另外两种思路(全部来自于 \(\text{do\_while\_true}\),在这里万分感谢他):

  1. 使用 \(\text{multi\_set}\),作用和优先队列差不多(抛开对比优先队列的常数),但是支持迭代器 \(\text{erase}\),删除的时候直接 \(\text{lower\_bound} + \text{erase}\),查询最小只需要 \(\text{begin}\) 就行了。时间复杂度依旧是 \(O(M \log n)\)
  2. 可删堆的思路,似乎常数更小?均摊下来依旧是 \(O(M\log n)\)

Challenge 9:

一道非常经典的 trick 好题

我们将每个线段树节点维护两个信息,分别是当前位置的数字和当前区间的有效元素个数,此时删除操作就变成了减少某个位置的有效元素个数,查询操作就变成了用当前的有效元素个数信息来凑出第 \(k\) 个数。

Challenge 10:

值域线段树或平衡树,原理同 Challenge 9。

注意查询右子树的时候应该是 \(k - \text{左子树的cnt}\)

Challenge 11:

主席树板子(雾),没啥技术含量(大雾),只要对线段树足够了解可以直接现魔改。

Challenge 12:

主席树板子二……

拓展:如果带单点修改怎么办

即答:主席树套树状数组

posted @ 2023-02-16 16:51  Larry76  阅读(112)  评论(0)    收藏  举报