树形数据结构

trick 整合

  1. 数据结构就是用来辅助维护的,把他当做一个壳子看待,看看能不能帮助做一些东西。

  2. 我们对于一些式子如果求和这种套的比较多,看看能不能写成递归形式。

  3. 对于 \(x \bmod y\),我们可以把它拆开 \(x - \lfloor\frac{x}{y}\rfloor\times y\),然后枚举 \(\lfloor\frac{x}{y}\rfloor\),计算贡献,复杂度可以做到 \(\log\),再加上一些数据结构维护即可。

题目

静态区间数颜色(离线)

将询问离线,按照右端点排序做扫描线。从 \(1\sim n\) 扫,维护每个位置 0/1 该元素是否是当前最后一个,每次将 \(last[a_i]\) 单点减,\(i\) 单点加,然后查询 \(l\sim r\) 的和即可。使用树状数组即可实现。

静态区间数颜色(在线)

用可持久化线段树,不过每次一样的维护,每次查询就是将版本 \(r\) 拿出来查询 \(l\sim r\) 即可。

QOJ7685. Barkley II \(\checkmark\)

题目大意

有 \(n\) 名学生,算法编号 \(1\sim m\),第 \(i\) 名学生掌握算法 \(a_i\)。选择一个学生编号的连续区间 \([l,r]\),评分为该区间内不同算法数量减去区间内未出现的最小算法编号(若所有算法都出现则为 \(m+1\)),求最大评分。
\(t\le 5\times10^5,\ n,m\le 5\times10^5\),所有测试用例 \(n\) 之和 \(\le 5\times10^5\)。

解题思路

我们枚举 \(a_i\) 并钦定 \(a_i=mex\),然后将 \([last[a_i]+1,i-1]\) 作为查询区间。其实实际上 \([last[a_i]+1,i-1]\) 的 \(mex\) 可能 \(<a_i\) 但是它不会影响答案,因为真实的答案区间一定更大,并且会在更小的 \(a_i\) 的统计到。

查询区间的数量是 \(O(n)\),现在问题就相当于是一个静态区间数颜色,可以离线做。

CF1553F Pairwise Modulo \(\checkmark\)

题目描述

给定一个长度为 \(n\) 的数组 \(a\),\(a\) 中元素两两不同,对于每个 \(k\in[1,n]\),求出

\[p_k=\sum_{1\le i,j \le k} a_i \bmod a_j \]

\(n\le 2\times 10^5, a_i\le 3\times 10^5\)。

解题思路

首先,肯定要将 \(p\) 的计算改为递推形式。

\[p_i = p_{i-1} + \sum_{j=1}^{i-1} a_i \bmod a_j + \sum_{j=1}^{i-1} a_j \bmod a_i \]

处理 \(\displaystyle\sum_{j=1}^{i-1} a_i \bmod a_j:\)

原式可处理为 \((i-1)a_i-\sum\limits_{j=1}^{i-1}a_j \lfloor\frac{a_i}{a_j}\rfloor\),令 \(c=\lfloor\frac{a_i}{a_j}\rfloor\),可以在之前计算 \(j\) 时,枚举 \(c\),对 \([c\times a_j,(c+1)\times a_j-1]\) 进行区间加 \(c\times a_j\),最后单点查询。可以用树状数组。

处理 \(\displaystyle\sum_{j=1}^{i-1} a_i \bmod a_j:\)

原式可处理为 \(\sum a_j - \sum\limits_{j=1}^{i-1}a_i \lfloor\frac{a_i}{a_j}\rfloor\),令 \(c=\lfloor\frac{a_i} {a_j}\rfloor\),枚举 \(c\),查询 \([c\times a_i,(c+1)\times a_i-1]\) 中 \(a_j\) 的个数 \(num\),统计 \(c\times num\times a_i\)。
\(\\\)
注意到 \(a\) 互不相同,所以总复杂度为 \(O(n \log n \ln n)\)。

启发:

  1. 我们对于一些式子如果求和这种套的比较多,看看能不能写成递归形式。

  2. 对于 \(x \bmod y\),我们可以把它拆开 \(x - \lfloor\frac{x}{y}\rfloor\times y\),然后枚举 \(\lfloor\frac{x}{y}\rfloor\),计算贡献,复杂度可以做到 \(\log\),再加上一些数据结构维护即可。

SS260804D. 冒泡排序二合一 (sort) \(\checkmark\)

题目大意

给定一个长度为 \(n\) 的排列 \(a\),每次有两种询问:

  1. 给定 \(l,r,k,x\),询问 \(a_l\sim a_r\) 进行 \(k\) 轮冒泡排序后,值 \(x\) 的下标是多少?

  2. 给定 \(l,r,k,x\),询问 \(a_l\sim a_r\) 进行 \(k\) 轮冒泡排序后,下标 \(x\) 的值是多少?

询问互相独立,每次冒泡排序后原序列会还原。

一轮冒泡排序就是对于 \(\forall l\le i\le r-1\) 如果 \(a_i>a_{i+1}\),那么交换 \(a_i\) 与 \(a_{i+1}\)。

\(1\le n,q\le 6\times 10^5\)

解题思路

冒泡排序有一些性质:

  • \(a_i\) 往前移动一位的次数是 \(\min(l\sim i中>i的个数,k)\)。

  • 每进行一轮冒泡排序都会把最大数往后丢(准确来说是尚未归位的最大数)。

  • \(i+k\) 个数进行冒泡排序 \(k\) 次就是将前 \(i\) 小放在前面,后 \(k\) 小丢在后面,注意后 \(k\) 小是全部已经归位的了,但是前 \(i\) 小不一定全都归位。

  • 你也可以对于每一层值域视作 01 序列来考虑,这样可能会好理解前面的,每层进行一次冒泡排序,每个 1 都会往后走,直到撞到下一个 1,此时现在的 1 就会停下来,下一个 1 开始继续往后走。

回到这道题,我们一下考虑的 \(x\) 都是在 \([l,r]\) 之间,如果不在,直接输出原来的就行,我们先考虑操作 2,就是查询下标:

我们很自然的想到运用它的性质,对 \([l,x+k]\) 求出前 \(x\) 小,但是前面也说了,前 \(x\) 小不一定顺序确定,有一个这样的解决方法:
先判断 \(a_{x+k}\) 是否是 \([l,x+k]\) 前 \(k\) 小,如果是,那么它在只能移动 \(k\) 次的条件下会移动到位置 \(x\),如果不是那么查 \([l,x+k-1]\) 第 \(x\) 小,因为这个位置属于后 \(k\) 小,所以它一定是有序的。

查询区间第 \(k\) 小是经典的主席树问题,\(O(n\log n)\)。

现在考虑操作 1,对于区间 \([l,r]\),分类讨论一下:

  • 比 \(x\) 大的数有 \(cnt\) 个,如果 \(cnt<k\),那么 \(x\) 肯定在冒泡排序后位于 \(r-cnt\)。

  • 现在 \(cnt\ge k\),如果在区间 \([l,pos_x]\) 的比 \(x\) 大的数至少有 \(k\) 个,每一轮往前进 1,那么 \(x\) 的位置 \(k\) 轮排序后肯定位于 \(pos_x-k\)。

  • 考虑完前两种情况后,只剩下在区间 \([l,pos_x]\) 的比 \(x\) 大的数个数 \(c\) 不到 \(k\) 个的情况了,现在 \(x\) 会先往前挪 \(c\) 个位置,再往后挪 \(k-c\) 次,结合前面说的第 4 条性质,每次都是移动到下一个 1 排序前的位置然后停下。具体实现上就等价于是在 \(x\) 这个值域上先找到 \(l\) 往后的第 \(k\) 小,然后减去 \(k-c\) 就是 \(x\) 往后移动到的位置,不要忘记要减去 \(c\)。

所以只需要建立对于每个值域维护下标位置是0/1的主席树,支持查找第 \(k\) 小和区间和即可,复杂度同样是 \(O(n\log n)\)。

详细实现看 oj 代码吧。

启发:

  • 首先,需要分析一些特殊性质。这道题很明显是要分析出冒泡排序的重要性质,赛时我只想到了第一个性质,就是往前移的性质,但是我没有想到对于解这道题最重要的往后移的性质。往前往后是对称的,这启发我们如果分析了当前方向的性质但是并无太多做题帮助,不妨换个角度思考一下,换反方向思考分析,这句话已经说了很多遍,但是真正场上并不能深入贯彻,这是需要提高的地方。

  • 这题同时提供了一个分析问题性质的角度,我们可以讲这种排列题转化成 01 序列来考虑,似乎比较有启发性。

  • 这道题中出现了两种主席树的建法,一种是对不同下标维护值域,另一种是对不同值维护下标区间,做题时要灵活运用不同的维护方法。

XMS2D. 神谕(oracle) \(\checkmark\)

题目大意

给定广义线段树和初始序列 \(A\)。支持两种操作:区间加 \([l,r]\) 增加 \(k\)(标准线段树区间加,下传标记并更新节点标记);询问某个恰好为线段树节点的区间 \([l,r]\) 上的标记值。输出所有询问答案。
\(n,q \le 2\times 10^5,\ 0<A_i,k\le 10^3\)。

考场思路

直接模拟,可以获得高达 20 pts。

解题思路

以下说的时间顺序就是按照操作顺序。

比较难处理的是 push_down 操作,一个节点 \([l,r]\)它的权值变化其实有两个影响因素,是它父亲最近被击破的时间 \(S\),还有它自己最近被击破的时间 \(T\)。被击破就是要往下push_down,也就是遇见了一个操作区间 \([l',r']\),操作区间与节点区间有交,但是它不包含节点区间。统计就是:维护 \(f_i\) 表示当前下标 \(i\) 这个位置累加的前缀和,那么当前询问区间在某个时间点的贡献就是 \(f_l(S)-f_l(T)\),这个可以用树状数组维护,这里的 \(l\) 也可以换成任意一个 \(i\in [l,r]\),因为这时整体 \([l,r]\) 区间加,不存在 \(l<l'\le r'<r\) 有区间加,否则 \([l,r]\) 就被击破了。

于是我们可以对每个询问区间离线下来,算出它的 \(S\) 和 \(T\),然后就变成扫描线扫时间就行了。

那么如何算出查询操作的 \(S\) 和 \(T\),对于每个操作,其实我们只用到它的 \(l,r\),我们用一颗线段树维护每次区间加操作 \(l,r\) 的时间点,每次就是查自己和父亲被击破的时间即可,线段树维护 \(\max\),查就是查 \(max\{l\in [p+1,q],r\in [p,q-1] 的时间点最后的区间\}\) 简化一下就是 \(\max\{t[p+1,q-1],t[1\sim p](以 p 为右端点),t[q\sim n](以 q 为左端点)\}\)。

具体实现看代码,复杂度是 \(O(n\log n)\)

启发: 题面有点吓人,但是不要被它牵着鼻子走,这题根本不是模拟题。我们应当静下心来,考虑问题的本质,比如在这道题中,问题的本质就是 push_down 的时间,然后进行扫描线。

一道模拟赛题

题目大意

给定一颗 \(n\) 个点的树,每个点有个颜色 \(c_i\),有 \(q\) 次询问,每次给定 \(u_1,v_1,u_2,v_2\),要求求出最大的 \(K\),使得 \(\forall i\in[1,K]\) 都有树上 \(u_1\sim v_1\) 路径上 \(c_i\) 数量和 \(u_2\sim v_2\) 路径的相同。

\(n,q\le 10^5\)

解题思路

posted @ 2026-09-17 20:21  lcx_OIer  阅读(12)  评论(0)    收藏  举报