Loading

2022.6 杂题

P6105 [Ynoi2010] y-fast trie

维护一个多重集 \(S\),表示当前集合中的元素 \(\bmod C\) 的值。

设最优的那两个元素是 \(x,y\),有两种情况:\(x+y\ge C\)\(x+y<C\)。对于第一种情况,把 \(S\) 中的最大值和次大值加起来即可。

对于第二种情况,考虑对每个数 \(a\) 维护 \(\operatorname{best}(a)=b\),表示最大的 \(b\in S\) 使得 \(a+b<C\)\(a\)\(b\) 不是同一元素。那么这种情况的答案就是 \(\max_{a\in S} \{a+\operatorname{best}(a)\}\)。用另一个多重集 \(S'\) 维护所有的 \(a+\operatorname{best}(a)\)

但这种做法会导致加入、删除一个元素时,可能改变 \(\mathcal{O}(n)\) 个元素的 \(\operatorname{best}\)。考虑只维护双向的最优匹配,也就是说,仅当 \(\operatorname{best}(\operatorname{best}(a))=a\) 时,才把 \(a+\operatorname{best}(a)\) 加入 \(S'\),这样对于每个数只存在至多一个匹配。时间复杂度 \(\mathcal{O}(n\log n)\)

思路挺简单的,但代码细节比较多。代码

P3215 [HNOI2011] 括号修复 / [JSOI2011] 括号序列

我的做法比较麻烦。在一段括号序列中删除掉匹配的括号后,剩下的一定是若干 ) 后面接着若干 (。那么用平衡树大力维护原来剩下的 )( 个数、Swap 之后的个数、Invert 之后的个数、既 Swap 又 Invert 之后的个数,一共 \(4\times 2=8\) 个值。然后直接做。设剩下了 \(a\)(\(b\)),那么答案就是 \(\lceil a/2 \rceil+\lceil b/2 \rceil\)

写错的地方:Swap 和 Invert 的标记是 ^= 而不是 =

更简单的做法是把 ( 设为 \(-1\),把 ) 设为 \(1\),那么剩下的 ) 个数就是前缀 \(\max\),剩下的 ( 个数就是后缀 \(\min\) 的绝对值。这样只需要记录前缀 \(\min/\max\) 和后缀 \(\min/\max\) 即可。代码

P4097 [HEOI2013] Segment

李超线段树。我以前居然没写过,不过挺好写。

开一棵线段树 \(T\),在每个节点上记录它的“主线段”,也就是包含这个区间、并且在最高折线中的长度最长的线段。加入一条线段 \(l'\) 时,对于被它完全包含的一个线段树节点,设这个节点上原来的主线段是 \(l\),有若干情况:

  • 在左端点、右端点处,\(l'\) 均比 \(l\) 低:啥也不干;
  • 在左端点、右端点处,\(l'\) 均比 \(l\) 高:把主线段换成 \(l'\)
  • 在中点处,\(l'\)\(l\) 高:把主线段换成 \(l'\),同时在线段树的某一侧把 \(l\) 递归下去;
  • 在中点处,\(l'\)\(l\) 低:主线段不变,同时在线段树的某一侧把 \(l'\) 递归下去。

这样,我们在 \(\mathcal{O}(\log V)\) 个线段树节点上插入线段,每个线段树节点最多递归 \(\mathcal{O}(\log V)\) 层,总复杂度是 \(\mathcal{O}(n\log^2 V)\),并且跑得很快。代码

还可以扩展到区间查询:再开一棵线段树 \(T'\),插入线段时就把线段的两个端点放到 \(T'\) 里。查询时,在 \(T\) 上查询区间的两个端点处的值,并且在 \(T'\) 上查询整个区间的最大值,然后把这两个答案取个 \(\max\)

P5610 [Ynoi2013] 大学

很厉害的并查集用法。

首先,假如 \(1\) 不作为除数,那么一个数最多被除 \(\log\) 次。要做的是快速找到所有需要被除的位置,考虑开 \(V\) 个并查集,第 \(x\) 个并查集中的 \(u\) 号点,它的根指向 \(u\) 后面第一个能被 \(x\) 整除的位置。这样,除的时候只需要二分一下找到并查集中的节点,然后遍历,并且每个数 \(x\) 都最多只会被插入到 \(\operatorname{d}(x)\) 个并查集中。并查集用路径压缩实现。

时间复杂度算起来挺麻烦,反正不超过 \(\mathcal{O}(n\sqrt{V}+nd\alpha(n))\),其中 \(d\) 是最大约数个数。代码

HDU 6315 Naive Operations

给定序列 \(\{a_n\}\)\(1\sim n\) 的排列 \(\{b_n\}\),需要支持两种操作:

  • \(a\) 区间加 \(1\)
  • 询问某个区间 \([l,r]\) 内的 \(\sum_{k=l}^r \lfloor\frac{a_k}{b_k}\rfloor\)

在最坏情况下答案是 \(n\ln n\) 级别的,所以只需要考虑维护出,在每次操作后,哪些位置的答案加了一。

用线段树维护所有位置距离下一次 \(+1\) 的差值,也就是 \(b_i-(a_i\bmod b_i)\)。维护区间最小值,假如最小值变到了 \(0\),说明存在一些会 \(+1\) 的位置,递归下去找到这些位置,然后更新答案、并且把这些位置的值重新设置成 \(b_i\) 即可。时间复杂度 \(\mathcal{O}((n+m)\log^2 n)\)

LOJ 6029 [雅礼集训 2017 Day1] 市场

对于一次区间除 \(d\) 并下取整的操作,设区间最大值为 \(a\)、最小值为 \(b\),假如 \(a-\lfloor a/d\rfloor=b-\lfloor b/d\rfloor\),那么直接在这个区间上打一个减法标记即可。否则递归到两边。

这样的话,每次递归到两边都会导致 \(a,b\) 均至少减半,最多只会减半 \(\log (a-b)\) 次。

对于一次区间 \([l,r]\) 加一个数的操作,被 \([l,r]\) 完全包含的线段树节点不会受到影响,只有那些与 \([l,r]\) 有交、但不完全被包含的节点才会受到影响。而一次的影响是极差加上了一个数,新增的代价就是 \(\log\) 级别的。

所以总时间复杂度是 \(\mathcal{O}(q\log n\log |C|+n\log V)\)代码

P5068 [Ynoi2015] 我回来了

https://www.luogu.com.cn/blog/alan-zhao/solution-p5068

P5586/P5350 序列

可持久化平衡树。我写的是无旋 Treap。

除了“区间复制”操作,其他操作普通平衡树都能做到。对于区间复制,我们将 Split 和 Merge 操作可持久化即可。

Pushdown 时,我们也需要复制子节点,因为当前的儿子可能是另外某个子树的。但是,如果当前节点上没有 tag,或者左、右儿子为空,那就不需要复制子节点了。

此外,可持久化平衡树的空间复杂度是 \(O(m\log n)\) 的,我们需要每 \(m/\log(n)\) 次操作重构一次平衡树,这样空间复杂度就是线性了。

代码

posted @ 2022-06-04 12:16  Alan_Zhao_2007  阅读(75)  评论(0编辑  收藏  举报