数据结构题目记录

P5309 [Ynoi2011] 初始化

修改次数与修改周期乘积 \(\le n\) 。修改次数 $ \le T$ 时候直接暴力修改,修改次数 \(\ge T\) 时候说明每次修改的间隔很短。可以维护每一个修改周期下的修改位置。其实题目本质上就是在 \(mod\) \(x = y\) 的位置上增加值,于是对于每个模数 \(x\) 维护取模后值的前后缀和统计即可。

P4688 [Ynoi2016] 掉进兔子洞

P5355 [Ynoi2017] 由乃的玉米田

P4692 [Ynoi2016] 谁的梦

P7447 [Ynoi2007] rgxsxrs

P9371 [APIO2023] 序列

首先枚举中位数然后根据相对大小 \(-1\) \(0\) \(1\) 的转化。设此时前缀和为 \(s_i\),枚举数的个数是 \(c_i\)。那么对于枚举 \(l\),显然就是要找到满足要求的最大 \(c_r\)。有 \(\lvert s_r-s_{l-1}\rvert \le c_r-c_{l-1}\)。拆开绝对值号转化一下就是 \((c_l+s_l,c_l-s_l)\) 右上方的点,那么直接偏序即可。注意细节这里不需要使用二维数据结构或三维偏序,因为要求的是最大的 \(c_r-c_{l-1}\),且 \({c_i}\) 单增,所以第一维不需要考虑大小关系。直接对于第二维排序,然后第三维树状数组即可。这样子的话时间复杂度是 \(O(n^2\log n)\) 的。
这里有一个根号分治的做法,对于出现次数大于 \(\sqrt n\) 的数直接执行上述二维偏序。对于出现次数小于 \(\sqrt n\) 的数,我们发现中位数出现位置端点组数是 \(O(\frac{n}{B}*B*B)\) 级别的那么直接枚举左右端点 \(pos_L\)\(pos_R\) 即可,此时固定左右端点后的 \(c\) 是定值,我们此时不要带入二维数点的式子,而是应该带入原始式子。这样发现由于要求的 \(\max c\) 固定,所以我们只需要判断最小的 \(\lvert s_r-s_{l-1} \rvert\) 是否小于 \(c\),其中 \(l \in (pos_{L-1},pos_L-1)\)\(r\in(pos_R+1,pos_{R+1})\),直接找似乎不太方便,用到性质这里的 \(s\) 是连续变化的,只需要维护最大最小值,就可以在两个区间内分别找到值域范围然后可以轻松解决。其中至于 \(-1~0~1\) 的处理我们不需要每次遍历序列处理,只需要从小到大的枚举待处理中位数,每个数只会被修改 \(O(1)\) 次,这样均摊操作线性。
考虑正解
解法一:顺着根号分治的第二情况思考,这是一个最大化区间的过程,于是我们考虑能不能用双指针维护,发现真的有单调性。这里的单调性并不显然,因为最后我们选取的产生中位数的区间和枚举的左右端点并不完全一样,但是更小的区间意味向外扩展着更多的选择,如果包含它的大区间满足,那么小区间必然可以通过向外扩展满足条件。

解法二:顺着根号分治的第一种情况思考,我们发现其实无用状态太多了,明明一个数能产生实际贡献的位置很少,但是我们对于每种数还需要遍历整个序列十分浪费。每个位置实际产生的贡献又有一些微妙的变化,不能直接忽视,不过变化连续都是 \(+1 -1\) 这样的变化。对于这种情况,我们考虑将上述的 \((c_i+s_i,c_i-s_i)\) 画在坐标系中,因为图象可以反应不同点之间的规律,合并相同信息。设当前枚举的中位数为 \(x\),发现如果 \(a_i<x\),那么会往左上走,\(a_i=x\),会往右上走,如果 \(a_i=x\),会往右下走。两条直线贡献即为截距之差,思考两条直线何时可以产生贡献,只要截距大的那条直线存在一点在截距小的那条直接右上方就行了,只需要直线的最大横坐标和最大纵坐标分别大于另一条直线的最小横坐标和最小纵坐标即可,转化一下就是二维数点。我们发现直线个数取决于 \(c\) 的变化次数,\(c\) 每次变化相当于出现一次枚举的中位数,那么对于每个中位数枚举每条直线复杂度均摊线性。

P6688 可重集

这种难以维护信息显然是要用哈希判断相等的。

还要在加 \(k\) 之后判断相等,于是我们可与先考虑确定下来所加数\(k=\min\limits_{i \in [l2,r2]}a_i-\min\limits_{i \in [l1,r1]}a_i\)

这其实就是需要我们设计出一个可以进行加法的哈希。

  • subtask 1 直接模拟即可
  • subtask 2
    发现值域很小,于是我们可以对每一种取值都维护一个树状数组。判断两个区间是否相等,直接看区间内某种数出现次数是否相等。这样子时间复杂度为 \(O(nV\log n)\)。我们可以考虑分块,维护每种值下所有块间的前缀和。对于修改直接对于所涉及的两种值进行块间前缀和的暴力修改。对于询问直接枚举取值,然后大块用块间前缀和解决,小块暴力扫描。时间复杂度 \(O(n\sqrt n+nV)\)

考虑一个可以支持区间自变量加法的函数,这里可以考虑指数函数 \(f(x)=a^x\),直接将 \(x \to a^x\),当我们想要加的时候,只需要对于指数函数的求和式子乘上一个数就行了。小细节,树状数组可以维护区间和,但是无法维护最值,可是用线段树维护区间最小值太麻烦了,于是我们考虑用另一种方式求出 \(k\)。可以把两个区间和的差除以区间长度。

CF603E Pastoral Oddities

P8421 [THUPC2022 决赛] rsraogps

考虑扫描线,\(s_i\) 维护的是 \(l'\le i\)\(r'\le r\) 的权值和。每次扩展时加入区间 \([1,r],[2,r]...[r,r]\),我们要动态维护这一部分贡献,发现贡献不是类似区间和/最大值之类的东西,而是三个东西的杂糅不太好直接维护。这里有一个技巧,\(\gcd \operatorname{and} \operatorname{or}\) 的变化都是至多 \(\log\) 级别的于是我们可以暴力往前扫知道三个值全部都不变了。

然后对于变化的值我们暴力修改,同时其余那些没变的 \(s_i\) 每次都是增加一个常量。直接维护即可。

P3792 由乃与大母神原型和偶像崇拜

我们可以发现如果区间值域连续,我们是可以确定下来值域就是 \([\min a_i,\max a_i]\)。等于是判断区间集合是否相等,直接随意映射+异或就可以解决。或者我们可以用线段树维护区间 \(k\) 次方之和。

P5445 [APIO2019] 路灯

发现联通 \(x-x+1\),就等于把左右两段区间给联通了,设这段区间为 \([l,r]\),那么本次联通对于跨过中点的点对都有贡献,这相当于对于一个左下角为 \((l,x+1)\),右上角为 \((x,r)\) 的矩形进行了一个矩阵加,至于加了多少,就取决于何时断开连接,大概就是一个 \(T-t\) 的贡献。这是一个三维偏序,可以用 CDQ 解决。

P4065 [JXOI2017] 颜色

首先对问题进行一个等价转换,其实就是左边选一段右边选一段,那我们考虑中间一段,不就是求合法区间数目吗?
法一:随机化,就是如果某个颜色出现于该区间内,那么该区间应该包含这个元素的所有位置。假设元素 \(x\) 出现了 \(z\) 次,那么我们对于前 \(z-1\) 个赋一个随机权值,对于第 \(z\) 个赋值为前 \(z-1\) 个的异或和。这样就可以保证了合法区间异或和为 \(0\)。另外本题可以双哈希,提高准确度。

法二:经典套路:求区间个数可以转化为枚举右端点,然后用数据结构统计有多少满足条件的左端点。
对于每种颜色 \(c\) 求出 \(l_c~r_c\),分别表示该颜色最左边的位置和最右边的位置。设选择的左右端点为 \(L~R\),那么显然是要在 \(r_c>R\)\(c\) 中求 \(l_c \le R\) 的最大值,然后 \(L\) 必须大于这个值。而且对于 \(r_c \le R\) 的点需要满足 \(L \le l_c\) 或者 \(L>r_c\),直接将中间不合法区域线段树赋值即可。过程 \(1\) 我们可以反过来求 \(\max\limits_{j<R且r_j>R} j\),只需要维护一个栈即可。

ZROI2836.雷克雅未克

我们发现一个极大正方形肯定是被至少三个点卡住。可以发现每个点会扩展出 \(O(1)\) 个极大正方形,于是总共有 \(O(n)\) 个极大正方形。

如果我们对于 \(y\) 轴建立可持久化线段树,然后对于每个点二分一个 \(l\),在可持久化线段树的 \([y,y+l-1]\) 区间内寻找 \(x\) 的前后驱记为 \(x_1\)\(x_2\),然后直到找到 \(x_2-x_1=l\)。如果找不到说明不存在或者有一个卡着上界左右只存在一个点。这是 \(2\log\) 的,如果我们左边扫一下,右边扫一下再在两颗线段树上同时二分是 \(1\log\) 的。

然后就是对于每个询问点和矩形的匹配了,如果传统套路线段树上节点放 set 是 \(2\log\) 的,发现如果一个矩形比另一个小还更早消失,那么是不优的。我们可以通过节点上维护单调队列来完成这个操作,复杂度也是 \(1\log\) 的。总的时间复杂度 \(O(n\log n)\)

注意要通过设置无限远的点来制造无限大的矩形,从而判断无解。

ZROI2841.秋海棠

在值域不不大的时候,线段树二分是显然的。

值域过大的时候其实并不是找规律,这种东西都随便给的询问也很难找规律维护,也还是是线段树二分,但是需要动态开点就行。

线段树一大重点就是维护信息。现在考虑如何快速修改,直接对于每个节点打上标记 \((x,y)\) 表示下标 \(\bmod 2^x=y\) 的位置保留,假如新传入标记 \((1,1)\),原先的保留下来的是 \(2^xk+y\),注意区间剩余位置奇偶性只和 \(k\) 有关,和 \(y\) 无关。现在就是 \(2^x(2k+1)+y=2^{x+1}k+y+2^x\),对应修改即可。

附带剪枝,防止 \(x\) 过大,当区间个数为 \(0\) 的时候我们不改变 \(x\),当区间个数为 \(1\) 的时候,新标记保留首个元素,不改变 \(x\),这样子 \(x<2^{60}\)

附录

  1. 可以处理掉一个维度的方法:排序,cdq分治(保持其他维度的情况下分为两部分,使用双指针算左右之间的贡献),线段树分治,数据结构,可持久化,数据结构的嵌套(动态某某,平衡树套某某),二进制分组套某某

  2. \(swap(a_i,a_{i+1})\),直接更改 \(a_i\)的主席树。

posted @ 2024-01-06 23:53  Mirasycle  阅读(3)  评论(0编辑  收藏  举报