十月基础数据结构题没做
P9695 [GDCPC 2023] Traveling in Cells
本题显然就是找 \(x\) 左边和右边第一个不在 \(S\) 内的点的位置,然后区间和。考虑 \(k=1\),此时直接线段树二分即可。考虑 \(k=2\),对每个颜色动态开点线段树,线段树二分时把两个颜色的当前点都记下来,看看两个颜色的点的大小之和是否等于区间长度即可,并且这玩意可以轻松推广到 \(k\le 10^6\)。时间复杂度 \(O((n+q+k)\log n)\)。
CF280D k-Maximum Subsequence Sum
如果 \(k=1\) 显然就是小白逛公园板子题,\(k>1\) 考虑反悔贪心,每次找到最大子段和后将这个子段乘 \(-1\) 然后再做最大子段和,选中被乘 \(-1\) 的点表示我反悔了选这个点,正确性来源于感性理解或者模拟费用流。线段树维护最大最小子段和、前缀和、后缀和以及区间和,时间复杂度 \(O(nk\log n)\)。
P5356 [Ynoi Easy Round 2017] 由乃打扑克
看到区间 \(k\) 小想到二分答案,我们要检查 \(\le mid\) 的个数是否 \(\ge k\),考虑分块,块内维护排好序的序列,修改对于整块打标记,对于散块把排好序的序列中被修改的数拎出来然后归并(懒得写归并可以使用 C++ 自带的 merge
函数,用法是 merge(l1,r1,l2,r2,l3,cmp)
表示将指针 \([l1,r1)\) 和 \([l2,r2)\) 内的元素归并并储存在指针开头为 l3
的位置中,需要保证被归并序列按照 cmp
排好序)可以降低常数。查询对于整块自然是二分找 \(\le mid\) 的个数,散块考虑将被查询内容拿出来排好序(由于只有两块散块,所以照样可以归并,可以使用 C++ 自带的 inplace_merge(l,mid,r)
表示将 \([l,mid)\) 和 \([mid,r)\) 归并后放在 \([l,r)\),需要对同一个序列做)后和整块一样的操作在上面二分,这样也可以减少常数。设块长为 \(B\),复杂度是 \(O(\frac{n^2}{B}+B+\log V\frac{n^2\log n}{B}+nB)=O(\frac{n^2\log n\log V}{B}+nB)\),取 \(B=\sqrt n\log n\) 时间复杂度 \(O(n\sqrt n\log V)\)。
P3714 [BJOI2017] 树的难题
考虑点分治,一遍 dfs 可以拿出子树内任意长度的最大权值,考虑合并两个子树,分同色和异色讨论(本质一样,区别是同色合并时有一个变化量),合并时枚举长度树状数组维护复杂度是 \(O(n\log^2n)\) 的,但是发现枚举其中一个子树内的长度另一个子树的合法长度是滑动窗口,单调队列维护,复杂度是 \(O(A+B)\),\(A,B\) 是两个子树的最大深度,为了保证复杂度正确,需要按照最大深度从小往大维护,这样每个子树的深度最多贡献 \(O(1)\) 次,时间复杂度 \(O(n\log n)\)。
P5309 [Ynoi2011] 初始化
考虑根号分治,令原题的 \(x\) 为 \(k\),\(k\ge \sqrt n\) 暴力加,需要根号平衡一下,\(k<\sqrt n\),为了方便维护这种情况时用 0-index,由于肯定是从第一个对每个 \(k\) 存一下模 \(k\) 每种下标加了多少,查询的时候就是一段区间或者若干个整段+一段前缀+一段后缀,修改的时候维护一下前缀和就好,复杂度 \(O(n\sqrt n)\)。
CF848C Goodbye Souvenir
把答案拆到每一个点上,就是 \(i\) 和 \(a_i\) 上一次出现位置的差,特殊的,\([l,r]\) 区间中第一次出现的点(即它的上一次出现在 \(l\) 之前)没有贡献。假设 \(pre_i\) 表示上一次出现位置,那么限制就可以表示为 \(pre_i\ge l,i\le r,val=i-pre_i\),是一个二维数点的形式,由于带修,考虑 cdq 分治,对于修改,一个比较好写的做法是,如果修改了,那么把它表示为一个点的删除和一个点的加入,这样只需要加删时刻晚于查询时刻即可,本来就没有贡献的点会被加一次删一次从而没有贡献。这样加上时间轴就是朴素的三维偏序了,时间复杂度 \(O(n\log^2n)\)。
tips:虽然最终 cdq 分治序列长度大概会在 \(5\times10^5\sim 8\times10^5\) 之间,但是 cdq 分治常数很小,所以能轻松通过。实践证明,\(10^6\) 规模的 cdq 三维偏序运行时间为两秒出头。