2022.12.31 数据结构 学习笔记
讲课人:许庭强。
由于 xtq 认为 lxl,zx 等人的题目在大型比赛中出现频率很高,所以这场也可以看作是 Ynoi 专场。
大部分题没有代码,请读者自行脑补()
Polylog
1.P6109 [Ynoi2009] rprmq1
首先先对第一维建出一棵线段树的结构,并类似猫树把每个询问挂到某一个节点的左右儿子上,这样就只要查左儿子的一段后缀和右儿子的一段前缀了,这也可以认为是分治。具体的,一开始是 build(1,n)
。如果当前在 build(l,r)
并且询问跨过了 ,那么就把询问挂到这个线段树节点的左右儿子上,否则扔到左边 build(l,mid)
或者右边 build(mid+1,r)
继续递归下放。
线段树的树高是 的,我们对每一层都做一次扫描线求答案。每一层扫描线又分为从左到右和从右到左各一次,从左到右扫时,我们统计查询前缀的答案;从右往左扫时,统计查询前缀的答案。扫的时候维护一棵线段树,线段树上节点存储当前第一维坐标为目前扫到的位置,第二维为该节点对应的区间的信息。从左往右扫时,我们把矩形加差分变成在矩形的左边界处加,在矩形的右边界处结束后减,这在线段树上对应的是区间加,同时还要维护历史最大值,以便前缀查询时需要。为了不同节点之间互不干扰,我们在扫到一个新节点时要给全局加 。
每层扫一遍复杂度是 的,每个查询只会在某一层的某个地方被 统计。故总时间复杂度 ,空间复杂度 。
2.P8512 [Ynoi Easy Round 2021] TEST_152
一个数的值取决于它最后一次被覆盖时的值。考虑扫描线,假设目前扫到右端点为 ,设 最后一次被覆盖时间是 ,被覆盖的值是 ,对于查询 ,如果 ,那么答案加上 ,否则答案不变。因此,我们开一棵线段树维护右端点为当前扫到的位置,每个左端点的答案,那么 对应的就是把左端点 的部分都加上 。注意到每次修改相当于把区间的 二元组进行覆盖,这可以用珂朵莉树维护。
总时间复杂度 ,空间复杂度 。
3.P8337 [Ynoi2004] rsxc/P8283 「MCOI-08」Dantalion
是通过 的线性基中的数异或能得到的所有数的集合,即若设 的线性基大小为 ,则 。
对于每次询问 枚举 ,那么对于每个 作为右端点时,满足与该右端点形成的区间的线性基恰有 个元素的左端点是一段区间,满足与该右端点形成的区间中恰有 个不同元素的左端点也是一段区间,那它们的交即合法左端点集合必然也是一段区间,并且该合法区间的两端点都不降。
我们考虑在二维平面上,两位分别表示左端点坐标和右端点坐标,那么一个区间的所有子区间就是一个矩形。设当前询问为 ,我们可以通过预处理 找到一行 (对应右端点坐标)使得在这行之上所有 都满足 ,在这行之下所有 都满足 。再 找到一行 使得在这行之上所有 都满足 ,在这行之下所有 都满足 。对 两部分分别统计答案。前一部分直接求区间所有线段长度和,后一部分相当于求所有右端点之和减去 乘行数的积,这都可以预处理。
总时间复杂度 ,空间复杂度 。
4.P8336 [Ynoi2004] 2stmst/P8260 [CTS2022] 燃烧的呐球
如果 互为祖先关系,则 等与大的子树大小,否则等于两子树大小相加。考虑 Boruvka 算法,每次对每个点找到连通块外与其相连最小的边。对于 所找到的点 分几类讨论。
第一种情况是找到的点满足 或 存在至少一者不为祖先关系,不妨设为 。那么与 有关的贡献 即为 。注意到不为祖先关系的贡献为 ,为祖先关系的贡献是 ,因此若选出的 为祖先关系相当于把答案算大了,对我们求最小值并不影响。在上述基础下,选出的 有两种情况:一种是 的贡献是 ,另一种贡献是 ,根据分析前者直接取最小的 即可。对于后者, 是 祖先的话在 dfs 的时候时刻维护当前点到根的这条链即可; 是 的后代的话直接取 子树中的某个 使得对应的 的 最大即可。均可在 时间复杂度内处理。
第二种情况是 , 都存在祖先关系。这时候又分三种:第一种, 都在 的子树内,那么我们以 这一维进行 dfs,同时用线段树维护 dfs 序表示对每个 的值目前是否出现过。那么 dfs 完 子树后在线段树上查一下 对应的子树区间内有无点,有的话就可以对答案贡献 了,线段树可以由所有儿子的线段树合并继承上来。第二种, 为 祖先, 为 祖先,那么我们还是根据 这一维来 dfs 并时刻维护当前点到根的这条链,那么我们要在链上找一点 使得 在 的子树里且 最大,这可以通过把这条链上的点加入维护 dfs 序的线段树中实现;第三种, 为 祖先, 为 祖先,还是根据 这一维来 dfs 并时刻维护当前点到根的这条链,那么我们要在链上找一点 使得 为 的祖先且 最大,这可以通过树剖+把当前点到根的路径拆成 个区间同上做,但这样会多一个 ,更好的方法是用全局平衡二叉树来维护,可惜我现在也不会。
如果用全局平衡二叉树的话,总时间复杂度是 的;如果用树剖的话,总时间复杂度是 的,空间复杂度 。
5.P7907 [Ynoi2005] rmscne
没讲。
6.P8265 [Ynoi Easy Round 2020] TEST_63
考虑用 LCT 维护森林的轻重链剖分结构。事实上,我们只需实现两种操作:1. 换根;2. 查询某个点的重儿子。原因是加边操作可看成把 换成根,连接 ,判断 和 的重儿子谁会成为 的新的重儿子,如果不是 就做完了,如果是 那就把 的重儿子设为 并让 等于 所在重链的链顶,重复上述操作。由于一个点到根的路径上轻边只有 条,所以加边操作会使用 次换根和 次查询。删边操作(设删去的边是由 指向 )可看成把 换成根,断掉 , 所在树的根换回原来的根, 重新查询其重儿子是谁,所以删边操作会使用 次换根和 次查询。那么总共会使用 次 操作和 次 操作。
对于换根,设当前根为 ,打算换成 ,那么把 MakeRoot
一下再把 Access
一下,此时除了 到 的路径上的点,其它点的重儿子一定都是对的(都保持原重儿子不变)。考虑 到 路径上的点,若 Access
完的重儿子为 ,但实际上并不是 ,那一定有 ,也即存在 使得 是满足 中最大的 ,此时 ,那么可以枚举 ,再在 到 的「重链」对应的 Splay 上二分出深度最大的点 满足 ,再查询 的重儿子是否为当前重儿子并做出相应修改,这样只需调用 次 操作即可实现 操作。
对于查询某个点的重儿子,实际上就是要维护子树信息,考虑 LCT 上每个点用 set 额外维护其所有轻儿子的子树大小和所在重链最大编号组成的二元组,再和其重儿子比较一下就可知其真实重儿子了。单次复杂度 。
由于 操作每次只会影响 条重链, 操作每次只会影响 条重链,所以再开个数据结构(线段树/平衡树)维护每条重链的权值,每当有修改就直接在数据结构里修改即可。
总时间复杂度 ,空间复杂度 。
7.P6780 [Ynoi2009] pmrllcsrms
个一段分块。分完块后考虑查询,查询出来的区间包括两个散块和中间一些块长为 的整块(不然的话要讨论的情况只会减少不会增多)。考虑最大子段和会出现在哪,一种情况是完整的出现在整块或散块的内部,这是好求的,可以用线段树维护块内最大前缀和,最小前缀和,最大子段和,这样修改和散块查询也方便;查询时中间整块的答案可以用线段树维护。
另一种情况是出现在跨越相邻两个整块或跨越一个整块和一个散块,对于跨越两个整块,设在前一个整块的后缀长 ,在后一个整块的前缀长 ,即 ,令 ,即求 ,等价于 ,即 ,这可以用线段树维护相邻两个块在一个线段树上区间 内最大的 ,最大的 ,以及该节点最大的 。对于跨越一个整块和一个散块的情况,做一样的变换后发现查询的 只是左端点被限制要大于某个值,那么在线段树上区间查询即可。
总时间复杂度 ,空间复杂度 。
8.P6105 [Ynoi2010] y-fast trie
2022 年 APIO 时就看到 yc 爆切这题了。如果你理解了上一题,那么这题对你来说应该不算难!
将所有数先对 取模,只需讨论 是否 。如果大等于,取最大的两个出来再判断是否真的大等于 ;如果不大等于,那么答案为 ,注意到 等价于 ,即固定了 之后, 为 的前驱,那么令 ,要最大化 ,即 ,即 ,和上题一样用线段树维护一下即可。
时间复杂度 ,空间复杂度 。
9.P6018 [Ynoi2010] Fusion tree
个人觉得这题真的非常非常有趣!强烈推荐!
考虑每个点维护一棵 trie,把所有儿子上的数插入这棵 trie 中,但是插入的方式是从低位往高位插。这样 操作时父亲特殊改一下,儿子就相当于该点的 trie 中的数全 ,考虑最低位为 的数,加一后变成 ,而最低位为 的数,加一后变成 且产生 的进位,这相当于交换 两棵子树后要在新的 那颗子树中递归做这个子问题,也就是说总共要翻转 次 trie 树。 操作没啥好说的。 操作无非就是在 trie 树的基础上再记个子树内数的个数,来辅助判断该位异或起来是 还是 ;同时,还需要父亲单点査,而单点査可以在修改时对每个点打标记记录儿子一共一起加了多少,从而还原出单点的真实值。
总时间复杂度 ,空间复杂度 。
10.P8531 [Ynoi2003] 戌亥彗星
没讲。
Sqrt
11.P8530 [Ynoi2003] 博丽灵梦
如果是一维数颜色问题,那么可以莫队,对每个位置记一个与其颜色相同的前驱,查询 就是问前驱在 中的颜色种数。
考虑二维,在纵轴上莫队,对于横轴维护前驱后继关系,我们每次加进来一个点只会影响 组前驱后继关系,如何快速找到被影响的关系是哪几组?考虑只删除莫队(类似回滚莫队),可以用链表 维护。于是问题变成了 次插入/删除单点, 次矩形查询,我们可以用二维分块做到 插入/删除单点, 查询。
二维分块的流程是:先把横轴和纵轴按照 和 的大小分两次块,这样一次矩形查询在横轴/纵轴上可以被分成不超过 段 ,剩余的部分又可被分成不超过 段 ,剩下的散块长度不超过 。那么整块()数是 的,对于散块,由于所有点横纵坐标互不相同,所以每行/每列最多只有一个点,可以 统计。
总时间复杂度 ,空间复杂度 。
12.P8528 [Ynoi2003] 铃原露露/P8419 [THUPC2022 决赛] riapq
每次修改操作 ,相当于对每个 , 加上 且 比 小的个数,这又等于 且 比 小的个数减去 且 比 小的个数。
前者可以用树状数组预处理出,每次修改操作时对修改的区间加上 ,表示这个区间内的 都要加上一次 且 比 小的个数的贡献,这是 的(也可以用分块做到 )。
后者考虑对序列分块,每 个一块,这样 就被分成了 个整块和一个长为 的散块。对于整块我们对每对 都预处理出前 个整块中 比 小的贡献,这可以通过把前 个整块中的数拎出来做个前缀和再单次 查询,复杂度是 的。
对于散块考虑建一个二维坐标系,每个点有一个权值,其中 上的权值表示所有散块对 的贡献。散块中的每个二元组 对二维坐标系的贡献是对第一维在 且第二维大等于 的地方都加 ,那么这就变成了 次矩形加, 次单点査,差分一下变成 次单点加, 次矩形査。但是这次不能直接用二维分块,因为上一题有所有点横纵坐标互不相同的条件,所以散块暴力做的复杂度是对的,这题我们考虑对横坐标 CDQ 分治,每次计算分界线左侧的单点加对右侧的矩形査的贡献。对纵坐标维护一棵线段树,分界线左侧的单点加和右侧的矩形査再转换成分块上的 单点加和 区间査,那么单层总复杂度为 的,一共有 层,所以这部分总复杂度为 。
如果第一部分用分块的话总时间复杂度为 ,平衡复杂度得最终时间复杂度为 ,空间复杂度可以第二部分分 次做做到 。
13.P8527 [Ynoi2003] 樋口円香
对 序列 个一段分块,每次操作散块暴力做,整块打标记。具体的,把整块从左到右从低位到高位看成一个多项式,每次平移就是乘上一个 的次幂再累加到 上,先把这个 的次幂记到该次操作影响的块上,所有操作结束后再做 次 一起计算。
时间复杂度 ,平衡复杂度得最终时间复杂度 ,空间复杂度可以利用扫描线的思想做到 。
14.P8526 [Ynoi2078] 《How to represent part-whole hierarchies in a neural network》阅读报告(更新中...)/P8257 [CTS2022] 普罗霍洛夫卡
对右端点进行扫描线,设当前扫到的右端点为 ,记二元组 的权值为 (如果把 看成颜色序列,即 内的不同颜色数),定义一些东西:;,其中 代表异或。那么右端点 每往右推一格, 的一段以 为右端点的后缀会被区间加 ,左端点是该颜色上次出现的位置;同时 ,即 更新完后 。我们用三个操作来表示我们要实现的东西: 操作:给 区间 ; 操作:所有 ; 操作:查询 区间异或和。
我们对序列每 一组分块,对一个块 维护下列信息:1. 区间内 被 的次数 ;2. ;3. ,;4. 和 ,表示块内的所有 要异或上 次形如 ,即还原真正的 需要将 。
那么 操作我们把散块重构,整块对 的 (第 种信息),若 则重构; 操作把 (用 两种信息更新第 种信息),(用第 种信息更新第 种信息); 操作散块重构还原出 ,整块直接査第 种信息。
注意到 操作中均有重构,对散块重构都是单词询问只有 次,对整块重构分析一下发现最多只有 次,所以总的重构次数只有 次,接下来我们尝试在 的时间内完成一次对一块的重构。
首先 的值是很好还原的,只要把所有 加上对应的 ,难点在于:1. 知道 后如何对每个 ,预处理出 ;2. 对每个 ,计算 。这两种信息如果能维护,那么就可以还原出 进而维护出第 种信息了。
对于前者,我们考虑 固定时第 位是什么(最低位为第 位),答案是( 的第 位)( 的第 位)( 的末 位 的末 位 ),前两个直接把所有 异或起来,再根据 的个数判断是否要再异或一个 ,关键在于最后一个。若 ,则 的末 位 ,最后一个括号变成判断 的末 位 ,我们对满足该限制的所有 一起计算。假设 在二进制下有 位(),设 去除末 位后剩下 ,则 的末 位与 相加后有可能往前进一位也有可能不进位,即 可能变成 或保持不变,如果把 按照最后 位从小到大排序,那么对每个 会向前进位的 是一段后缀,不会进位的一定是一段前缀,可以差分下 打个标记,枚举所有 可以 算出对于每个 有几个 能让其最后 位进位,对于一个数如果不进位之后肯定都不会进位,如果进位了之后进位的位数只与 末尾最长连续 的个数有关,也就是说会进位的 是一段连续的部分,这又可以差分一下打个标记最后一起算贡献。至此,对于 的 我们已经一并在 的时间内算出贡献了。
剩下 的部分,按 从大往小处理。考虑对所有 ,我们处理出如果 的最后 位是 ,有几个 能使它们的和发生进位。首先可以对所有 求出 表示 的末 位等于 的有几个数,这可以由 的结果在 时间内推得;然后对于一个 ,与其相加会进位的数需满足 ,是连续的一段,因此可以把 做个后缀和然后单次 求得贡献,总的时间复杂度也是 的。这样时间复杂度就是神奇的 !
对于后者,我们发现就是把前者的 换成了 ,把 的范围由 换成了 的范围 ,长度都为 ,把 换成了 ,把 换成了 ,整体形式和前者相同,也可用相同的方法解决,在此就不再赘述。
所以我们成功在 时间内实现了块的重构,总时间复杂度 ,逐块处理的话还是可以做到 的空间复杂度。
15.P8513 [Ynoi Easy Round 2021] TEST_136/P8261 [CTS2022] 袜子
没讲。
16.P7722 [Ynoi2007] tmpq
首先让 ,那么每次单点修改相当于 ,查询变成了查询 , 的 数。
考虑对在 和修改中出现过的 个数按照其总出现次数是否大于 分治,记 的出现次数为 , 表示 序列中最大的 使得 ,若不存在则视为 。对于 ,,设 表示有多少 ,, 表示有多少 ,,那么 ,,那么满足条件的 数即为 。
于是我们对每种 ,,都可以 求出所有 且 ,满足条件的 数 。设查询时只考虑 满足 ,右边界为 的答案为 ,那么 对 的贡献就是后缀加,查询 是单点査,有 次后缀加, 次单点査,可以用分块做到 的复杂度。
对于每种 ,,这样的数种数最多只有 种,对于每种数我们离线分别做。假设当前在处理 满足 的答案,设 表示有多少 ,, 表示有多少 ,, 表示有多少 ,,那么 ,,。也就是说如果知道了 ,那么乘一个 的矩阵就可以得到 ,那只要维护矩阵,修改是单点修改,查询是査区间乘积,设修改有 次(满足 ),查询有 次,还是可以用分块做到 的复杂度。把每种 的复杂度相加就是 。
总时间复杂度 ,平衡得最终复杂度为 ,空间复杂度 。
其他题目
17.UOJ#712. 【北大集训2021】简单数据结构
考虑如果 先进行了 次 操作,再对 取 ,之后又进行了 次 操作,相当于 ,那么最终的 序列可以看成先把每个 加上 ,其中 是总共进行的 操作次数,然后 对所有的 取 ,其中 是某次取 的值, 是该次取 后一共进行的 操作次数。可以发现若把 看成 个点,把 看成若干条直线,那么这些直线的最小值会形成一个上凸包,因为斜率递减所以这个凸包可以线性求,最后在凸包外的点会被往下拉拉到凸包上,而凸包内的点不变。
如果知道每个点什么时候被往下拉,那么可以把点分成两类,一类是被拉过至少一次的点,一类是没被拉过的点。如果实时维护上述凸包,也就是说 操作是加入一条直线, 操作是所有直线加上 ,那么可以发现,被拉过一次的点之后一定都在每个时刻对应的凸包上,所以每个点从不在凸包上被拉到在凸包上至多只会发生一次。我们用一棵线段树维护不在凸包上的点,它们的 之和,和它们的个数;再用一棵线段树维护在凸包上的点,它们的一次项系数的和和常数项的和。那么 操作对在凸包上的点是一个后缀推平,二分出分界点后区间覆盖即可,对不在凸包上的点我们知道它此时是否会变成在凸包上,即知道它是否要从一棵线段树上删去并在另一棵线段树上插入;对于 操作直接改就好了;对于 操作,两部分答案都求下加起来就好了。
最后的问题是怎么知道每个点什么时候第一次被往下拉到凸包上?考虑整体二分,实时维护凸包是 的,判断一个点是否在 时刻被拉下来过是 的,总共要判断 次,所以单次是 的。
总时间复杂度 ,空间复杂度 。
18.UOJ#715. 【北大集训2021】小明的树
xtq 表示是脑筋急转弯题,事实证明我的脑子转不过来。
首先发现树是美丽的等价于未点亮的连通块个数为 ,此时答案要加上点亮的连通块个数。由于连通块个数等于点数减边数,我们考虑用线段树维护每个时刻未点亮和点亮的连通块个数,首先每个时刻点亮和未点亮的点数是确定的,考虑一条边 ,设 分别在 时刻被点亮且 ,那么这条边对 时刻未点亮的连通块有的贡献,在 对点亮的连通块有贡献。删边和加边只会影响一条边,这也很容易用线段树区间加减维护。由于未点亮的连通块数时刻不小于 ,我们在每个线段树节点上维护区间内未点亮的连通块数最小值,以及未点亮的连通块数最小时点亮的连通块数之和。查询时要判断一下未点亮的连通块数最小值是否为 。
总时间复杂度 ,空间复杂度 。
19.CF1764H Doremy's Paint 2
没讲。
20.Gym103914I Equivalence in Connectivity
没讲。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通