1.3 数据结构算法策略
\({\Large 约定}\):
- 用集合符号表示位运算符号,用 $ \oplus $ 表示异或,特别的,$ i \in S$ 表示二进制数 \(S\) 的第 \(i\) 位为 \(1\)
- 用 \(V\) 表示值域,\(\sum\) 表示字符集,\(\omega\) 表示
bitset的常数 \((\omega = 64)\)- 除去用 \(()/[]\) 表示开闭区间外,\([]\) 仅表示艾弗森约定,\(\{\}\) 仅表示集合,括号嵌套全用 \(()\)
- 字符串或序列角标为区间表示对应区间的子串
\(\textcolor{white}{\mathrm{pw:HellOWorlD}}\)
P2617 Dynamic Rankings
\(\textcolor{purple}{\mathrm{省选/NOI−}}\) tag 整体二分 树状数组
感觉看上去就很可以树套树啊?不过还是要学一下整体二分的。
显然如果我们一个一个二分的话时间复杂度会很爆炸,所以我们可以一起二分。我们用 \((l,r,L,R)\) 表示当前答案区间为 \([l,r]\),当前操作和询问序列区间为 \([L,R]\)。我们二分一个答案 \(mid=\frac{l+r}{2}\)。那么我们可以把操作序列分成答案 \(\leq mid\) 的和答案 \(>mid\) 的。那么要如何快去求出这个呢?显然这个可以用树状数组去维护。我们从 \(L\) 到 \(R\) 执行以下操作。假如说我们查询操作的结果 \(<k\) 或修改操作的 \(k>mid\),那么我们就把这些操作从左往右移到 \([L,R]\) 的开头,因为将 \(a_x\) 改为 \(y\) 后显然这个数是 \(\leq mid\) 的,说明有可能成为答案,否则就从右往左移到 \([L,R]\) 的结尾并且用树状数组进行对应的修改,这样保证了每个部分都维持了原先的相对大小关系。当然我们最后还要清空对树状数组的修改并翻转 \([L,R]\) 后半部分的操作因为我们移动的时候是倒着存的。完成这些之后就往下递归 \((l,mid,L,sl)\) 和 \((mid+1,r,sr,R)\)。边界条件是 \(l=r\)。
2025.12.22
CF896C Willem, Chtholly and Seniorious
2600 tag 数据结构
珂朵莉树板子题QWQ。
显然我们可以把这个序列分成若干段,每一段的颜色都是相同的。这样一次覆盖操作相当于从中选出若干段再合并成一个段,其他操作也一样暴力修改即可。当然要先把操作段分裂出来才行。
这个在随机数据的情况下复杂度是对的,好像期望是 \(\mathcal{O(\log n)}\),但是我不会证明QAQ。
2025.12.22
SP1043 GSS1 - Can you answer these queries I
数据结构
因为不带修改,所以可以用猫树喵。
原本的线段树是只记录当前这个区间的信息的。现在我们可以多记录一点东西:这个区间中点左边的最大后缀和以及最大子段和、这个区间中点右边的最大前缀和以及最大子段和。这样的话我们发现要求一个区间的最大子段和只需要将某个区间的两个信息合并一下就出来了,时间复杂度是 \(\mathcal{O(1)}\) 的。但是要怎么求出是哪个区间呢?我们发现一个点的编号就代表了它在树中所处的位置。而我们要求的那个点就是左右端点的 LCA。如何求出LCA的层数?我们发现这两个数的编号在二进制下会有一段相同的前缀,那么这个前缀的长度就是 LCA 的深度,只需要将两个数异或一下就能求出前缀的长度了。求答案就是左右两部分的合并。
2025.12.24
SP2916 GSS5 - Can you answer these queries V
数据结构
猫树大致的流程在上面,现在讲一下这题的做法。
我们把查询分成以下几类:
- 左右端点区间不重叠:显然就是中间部分+左端点最大后缀和+右端点最大前缀和
- 左右端点区间重叠且右端点区间在左端点区间后:以 \(l2\) 为界,前半部分后缀最大+后半部分前缀最大减去重复部分 \(a_{l2}\) 以及重叠部分的最大值。
- 左右端点区间重叠且右端点区间的最左边在左端点区间前:以 \(r1\) 为界,前半部分后缀最大+后半部分前缀最大减去重复部分 \(a_{r1}\) 以及重叠部分的最大值。
然后用猫树做就做完了。
2025.12.24
P6240 好吃的题目
\(\textcolor{purple}{\mathrm{省选/NOI−}}\) tag 分治 背包dp
猫树分治的应用。
我们发现假如说每次求一个人的答案是 \(\mathcal{O(t)}\) 的话,\(m\) 个人就是 \(\mathcal{O(mt)}\) 的,这是可以接受的!
考虑能不能用猫树实现 \(\mathcal{O(t)}\) 询问。我们发现如果只要求 \(1\) 个数的话是 \(\mathcal{O(t)}\) 的,这是我们想要的。
我们维护一个修改区间和询问集合。每到一个结点时,先用 \(\mathcal{O(200\times h_i)}\) 的复杂度预处理一下从中点向两边的背包。然后对于每个询问,如果说它跨过了中点,那么显然可以直接用计算好的背包合并一下求答案。否则的话把区间位于左边部分的询问放到左半边,把区间位于右边部分的询问放到右半边,然后再递归下去去做就好了。
时间复杂度好像是 \(\mathcal{O(nt\log n+tq)}\) 的。
2025.12.24
P3402 【模板】可持久化并查集
\(\textcolor{purple}{\mathrm{省选/NOI−}}\) tag 并查集 可持久化
呃呃虽然好像不知道有什么用。
显然这个并不能够用路径压缩,所以要用按制合并,我用的好像是按深度合并。因为要回退到某个版本,所以要用可持久化线段树维护 \(fa\) 数组。
\(find\) 的话只需要不停往上跳直到 \(fa_u=u\),\(merge\) 的话只需要把深度小的那个联通块合并到深度大的联通块上去,并将小联通块深度 \(+1\)。
好像就这样了。
2025.12.24
CF813F Bipartite Checking
2500 tag 数据结构 并查集 图论
线段树分治的板子题。
显然我们可以用可撤销并查集撤销一次操作,所以要是按制合并的并查集。因为撤销操作只能从栈顶往下做,所以我们要让插入/删除操作的次数尽可能少,尽量控制在 \(\mathcal{O(\log n)}\) 的级别,所以我们想到线段树。
因为一个区间在线段树上的段数不会超过 \(\log n\) 个,所以我们可以把操作放到线段树上去,这样对于一条边的复杂度是 \(\mathcal{O(\log^2 n)}\) 的,可以接受。
如何判断是不是二分图呢?考虑像食物链一样的做法,我们记录每个点和它的反点,因为一条边两端的点颜色不能相同,说明它的点和另一个点的反点是同一个连通块的,那么就把这两个点合并就好了,如果说某次合并没有成功说明不是二分图。按照线段树的递归方式往下做就好了。
2025.12.24
P3810 【模板】三维偏序 / 陌上花开
\(\textcolor{purple}{\mathrm{省选/NOI−}}\) tag cdq分治 树状数组
cdq 分治板子题(好像还有其他做法)。
把 \(a_i\) 看做第一维,\(b_i\) 看做第二维,\(c_i\) 看做第三维,假如说已经按照第一维排序了,那要想如何把后面两维也通过某种方式计算出答案。这时候就要用 cdq 分治了,其实感觉 cdq 分治和点分治有一点相似的地方就是两者都会选出一个分界点,然后先计算分界点两边/联通块之间的贡献,再计算每一边/每个联通块的贡献。
现在把序列分成两部分,要求左右两边的贡献,因为排序过了所以左半部分的 \(a_i\) 一定是小于右半部分的 \(a_i\) 的。所以我们可以按 \(b_i\) 排序,这样并不影响答案。好像就变成二维偏序了?在计算右半部分的答案的时候要把左半部分 \(b_i\leq b_j\) 的 \(i\) 都加到一个树状数组里,然后求 \(\leq c_j\) 的数的个数即可。因为 \(b_i\) 排序了可能会影响小区间的 \(a_i\) 相对大小关系,所以要先做小区间再做大区间。
2026.01.05
P5459 [BJOI2016] 回转寿司
\(\textcolor{green}{\mathrm{普及+/提高}}\) tag cdq分治
听说是 cdq 分治的题就来做了
显然有其他的做法,但还是要练一下 cdq 分治的。(其他做法也不会)
题目要求满足 \(L\leq \sum_{i=l}^r a_i\leq R\) 的区间 \([l,r]\) 的个数,我们可以对 \(a\) 求一下前缀和得到 \(s\),这样条件变成了 \(L\leq s_r-s_{l-1}\leq R\) 的区间 \([l,r]\),发现这个式子非常美观。但是由于 \(a_i\) 可能会 \(<0\),所以 \(s_i\) 不一定是单调的,这导致统计这个的时候会比较麻,考虑怎么 cdq 分治。
我觉得 cdq 分治想要求这些问题,要保证左右两部分区间是有序的,这样的话求二维偏序会方便不少。
具体流程与上面的差不多,区别在于如何计算贡献,显然可以从左往右枚举右半部分的点 \(k\),再用双指针维护一段区间表示 \(s_k\) 与这个区间的点的差是在 \([L,R]\) 这个范围内的,这样就可以计算出贡献了。
2026.01.05

浙公网安备 33010602011771号