【笔记】数据结构选讲 2025.2.10
【笔记】数据结构选讲-李雷思问 2025.2.10
Becoder 50751 多重集
G. 多重集 - 数据结构选讲1-李雷思问(day22) - 比赛 - Becoder
通过指定 \(a_c+b_c\) 与 \(a_y+b_y\) 的大小关系,可以得到一个一维偏序,满足这个一维偏序的时候 \(\max\) 取到某个值,反之取到另一个值。
在值域线段树上维护这个偏序关系,每次 pushup 的时候统计跨过中点的偏序关系所带来的答案。可以做到 \(O(q\log q)\)。
P7907 [Ynoi2005] rmscne
定义两个区间 \([l_1, r_1], [l_2, r_2]\)“值集合相同”当且仅当 \(\{a_{l_1}, a_{l_1+1}, \cdots a_{r_1}\}=\{a_{l_2}, a_{l_2+1}, \cdots a_{r_2}\}\)。
子区间的可能性过多,并不好直接求,我们考虑把选子区间改成:对询问区间 \([l, r]\) 的每一个后缀 \([x, r]\),都维护一个最短的前缀 \([x, y]\) 使得 \([x, y], [x, r]\) 值集合相同,然后记 \(f_{x, r}=y-x+1\)。然后我们再去找一个最短的后缀 \([z, r]\) 使得 \([z, r], [l, r]\) 值集合相同,我们查询 \(\min_{l\leq x\leq z}f_{x, r}\) 就好了。核心在于将一个子区间看作某个后缀的前缀。
扫描线从左往右扫 \(r\) 维护 \(f_{x, r}\),可以使用线段树维护。找到 \(z\) 可以维护所有颜色的最后出现位置的集合在上面 lower_bound 找。总的时间复杂度 \(O(n\log n)\)。
由于这个复杂度不是本题正解,需要优化常数才能通过。具体是优化找 \(z\) 的部分,发现那个集合可以提前加入 \([1, n]\) 所有位置然后每次只做删除操作。可以使用并查集维护。复杂度没有降低(仍然是 \(O(n\log n)\),注意),但是却神奇地通过了。
P6773 [NOI2020] 命运
一个 \(O(n^2)\) 的 dp 可以想一下就能想到,令 \(f_{u, j}\) 表示 \(u\) 子树中没有被满足的限制(没有已知黑色边的链)的最深链顶的深度,没有这样的链就是 \(0\)。转移首先初始化 \(f_{u, j}=[j=c]\) 其中 \(c\) 是以 \(u\) 为链底的最深链顶的深度。紧接着枚举所有儿子 \(v\),如果 \((u, v)\) 染黑则 \(j\) 会清零,否则不会动,也就是首先 \(f_{v, 0}\) 加上 \(\sum_{j}f_{v, j}\),然后做“max 卷积”:
一种方法是做前缀和之后点乘再差分回来,也可以拆开 \(\max\):
最后记得把 \(j\geq dep_u\) 的 \(f_{u, j}\) 全部清空,它们已经不合法了。这就是一个 dp。
优化使用线段树合并。合并的时候有一个函数 merge(p, q, l, r) 表示合并 \(p, q\) 两个线段树,你再额外传入两个变量 \(ps, qs\) 表示 \(<l\) 的部分 \(p\) 这边的总和与 \(q\) 那边的总和。这样如果 \(p, q\) 有一个为空的时候,例如 \(q\) 是空的,你就需要在 \(p\) 上打 \(\times qs\) 的乘法标记。叶子的时候,这片叶子的值更新为 \(val_pqs+val_qps+val_pval_q\)。其它情况正常递归下去并更新 \(ps, qs\)。(所以有可能你需要先合并右子树再合并左子树)
总复杂度 \(O(n\log n)\)。
[Hangzhou23K] Card Game
如果区间 \([l, r]\) 的答案为 \(f_{l, r}\),那么考虑 \(l\) 这边会干出什么事,设 \(l\) 右边第一个值与 \(l\) 相同的位置是 \(x\)。如果 \(r<x\),则 \(l\) 只出现一次且不会被其他东西消去,\(f_{l, r}=f_{l+1, r}+1\)。否则无论 \([l, x]\) 中间有什么,到了 \(x\) 都会消去,\(f_{l, r}=f_{x+1, r}\)。
不要写可持久化平衡树。直接做线段树合并与分裂就是对的了,因为这里的合并不是“有交并”而是“无交并”,复杂度是固定的 \(O(\log n)\),而分裂也是 \(O(\log n)\),因此总的复杂度是 \(O(n\log n)\) 的。这个东西带有标记,处理标记的方法参考可持久化文艺平衡树的模板。
UOJ164【清华集训2015】V
一种方法是和 P8868 [NOIP2022] 比赛 一样的矩阵乘法维护信息。另一种方法先忽略了历史最值,将所有操作统一刻画为了 \(x\mapsto \max(x+a, b)\)。由于这个刻画的性质过于好,它的复合与 \(\max\) 操作都是封闭的,对历史最值的操作也可以用这个刻画进行维护。复杂度 \(O(m\log n)\) 乘上一些常数。
LOJ6029 「雅礼集训 2017 Day1」市场
势能线段树。直接说了,这题的势能函数构造是 \(\phi(u)=\log_2\max(1, a_u-b_u)\) 其中 \(a_u, b_u\) 是线段树 \(u\) 节点的最大值、最小值。总势能是所有 \(\phi(u)\) 求和。我们希望一个事情发生:
但是很显然这不是很对,例子是 \(a=4, b=3, d=2\)。什么时候这会失效呢?令 \(a=pd+x, b=qd+y\),则其不成立的条件是:
这个条件看似十分搞笑,实际上也是没什么用(但其实有用),我们通过刚才的推导过程我们得知实际上以下式子是成立的:
意思是如果 \(x<y\) 会有一个负数出来,我们调整一下它。现在我们最害怕的事情就只有两个:
- \(a=b\) 的时候,向下递归无法减少势能,这需要区间赋值或区间加标记。
- \(a>b\) 的时候,势能有一定可能变成 \(\flr{(a-b)/d}+1\),如果这个东西 \(=a-b\) 我们就完蛋了,例如说 \(a=b+1\)(而且 \(x<y\))的时候势能就会不变,例如 \(a-b=2, d=2\) 而且 \(x<y\) 的时候势能也不会变,但你发现没有这种情况。再往上的情况那个 \(+1\) 就微乎其微了,因为 \(a-b\) 总会下降,势能总是能减少的。
那就只有两种情况:
- \(a=b\) 的时候一定要特判,打标记。
- \(a=b+1, x<y\) 即 \(p=q+1, x=0, y=d-1\) 的时候,因为 \(a-b\) 不变,所以我们打一个区间加的标记,同样的 \(a=b\) 的时候也打区间加标记就行了。
每次区间加操作会带来 \(O(\log n\log v)\) 的势能,每次区间除法操作只会减少势能,总时间复杂度 \(O(n\log v+q\log n\log v)\)。
***这里暂时跳过 segment tree beats 部分
Becoder 29550 牛半仙的妹子序列
总之,这是一个线段树优化 dp 的过程,抛开前面的平凡过程不谈(真的平凡吗?)。总之,我们需要的就是“在线段树区间上,维护出区间内的后缀最值位置上的 \(f_{q[i]}\) 之和”。
使用小粉兔线段树,在一个节点上维护区间最大值,后缀最大值的 dp 值之和,以及左子树中 \(\geq\) 右子树最大值的部分的后缀最大值的 dp 值之和(最后这一个值记作 \(s\))。pushup 的时候只需要确定 \(s\),为此我们拿着右子树最大值(记作 \(v\))进入左子树进行线段树二分,寻找左子树的后缀最大值中最靠右的 \(\geq v\) 的值,每次在一个节点上我们就是去看看右子树的最大值(记作 \(z\))和 \(v\) 的关系就能往一侧去递归,如果 \(z\geq v\) 则要往右子树走,同时说明此时左子树被 \(z\) 截断的部分现在也得以保留,我们即刻将那一部分(左子树的 \(s\))统计入当前的 \(s\) 中。如此就能计算出 \(s\)。最后总复杂度 \(O(n\log^2n)\)。
Becoder 46907 黑白树
先删掉这个改颜色的操作。由于有链加和子树加,我们猜测 dfn 序是跑不了了,而且一个连通块还可以表示为一个大子树扣掉一堆小子树,于是我们大概可以做了,然后发现复杂度有问题在里面。
有人提出了一种算法,以维护白色连通块信息为例,首先将所有黑色点(原文是黑色连通块顶点但我觉得也可以是黑色点)对应的那些子树在线段树上的 dfn 区间对应全都找出来,标记这些线段树节点为灰色,然后我们对白色连通块操作的时候,首先找到顶点(\(O(\log n)\) 的重链剖分就行),然后对这个顶点的子树进行线段树操作。然后神奇的地方来了,我们 pushdown 和 pushup 的时候,不让标记下传到灰色节点,也不从灰色节点把最值信息上传上来(当然还有普通的区间加要正常下传),这样就控住了白色连通块的范围,至此本题得解,时间复杂度 \(O(n\log n)\)。
什么你说操作 1 怎么办?让这个灰色区间能动态变化就行,并不难,然后这样修改之后由于标记都已经下传完了所以不再会有问题了。
树上数据结构部分
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/18709963
浙公网安备 33010602011771号