数据结构做题记录-2

数据结构做题记录-2

LG6109. [Ynoi2009] rprmq1

考虑对题目所问进行转换,将第一维看成时间,将第二维看成序列,那么操作就是在时间 \([l_1,r_1]\),序列的 \([l_2,r_2]\) 值会加 \(v\),查询时间 \([l_1,r_1]\),序列 \([l_2,r_2]\) 的历史最大值。对操作进行查分就是要维护区间加减和区间历史最大值,这个用历史最值线段树做就可以简单维护。现在考虑如何处理一个区间的历史最大值。

有一个 naive 的想法是钦定一些开始统计区间历史最大值的起点,然后对这些区间进行扫描线回答询问,这让我们联想到猫树:对中点进行前后缀的处理然后 \(O(1)\) 回答询问,因此不妨对时间这一维进行猫树分治,当一个区间越过中点时用处理出的答案回答询问。问题是我们每次查询都需要前面的所有操作支持,因此考虑下面的分治过程:首先加入 \([l,\text{mid}]\) 中的所有操作,接着更新区间历史最大值为当前最大值,接着依次进行 \([\text{mid}+1,r]\) 中的所有操作并更新答案(此时对应的 \([\text{mid},R]\) 区间一定在需要的 \([L,R]\) 区间内),然后撤销所有操作,调用右子树。接着先更新区间历史最大值维当前最大值,依次撤销 \([l,\text{mid}]\) 中的所有操作,在撤销之前统计答案的贡献(此时对应的 \([L,\text{mid}]\) 一定在需要的 \([L,R]\) 区间内)。这样做不难看出指针在 \([l,r]\) 内只会从 \(l\)\(r\) 移动一次,然后从 \(r\)\(l\) 移动一次,也就是进行区间内所有操作的正向反向均 \(1\) 次,每个操作被包含在 \(O(\log n)\) 个区间内,每个操作的复杂度是 \(O(\log n)\),因此操作的总复杂度是 \(O(m\log ^2 n)\),而对于答案的维护,每个询问只会在线段树上查询两次,因此复杂度是 \(O(q\log n)\) 的,总复杂度为 \(O((q+m\log n)\log n)\)

注意对于一个位置的所有操作应该先做减法再做加法,否则会提前更新区间历史最大值。

LG8496. [NOI2022] 众数

考虑这个问题可以简单使用摩尔投票的思路解决,然而问题是我们需要确定求出的答案是否符合要求,这启发我们用一个数据结构维护每一个序列中数字的个数,因此我们使用线段树维护,这样合并可以用线段树合并简单实现。
接着对于插入和删除的问题可以用链表维护每一个序列,这样可以做到 \(O(1)\) 求出删除的数字和合并两个序列,因此总复杂度是 \(O((n+q)\log V)\) 的。
需要注意和空序列有关的操作需要特判。

LG6780. [Ynoi2009] pmrllcsrms

考虑将序列对 \(c\) 进行分块,则贡献存在于单独的块和块与块之间。考虑对于单独的块实际上是容易求得的:对每一个块维护一个线段树,因为存在散块的查询,因此需要写一个区间最大子段和,然后接着用一个线段树维护全局最大值即可。
接下来考虑块与块之间的内容,实际上也不难,考虑因为长度不能超过 \(c\),所以当左边的块选到左区间时右边的块绝不可能选到右区间,相反也是同理,因此可以类似的维护左边块的和、右边块的和、左边块的后缀最大值、右边块的前缀最大值和最大子段和,依旧可以简单上传。考虑散块对整块、整块对散块、散块对散块的贡献互不相同:在某些区间下无法选择左边的内容或右边的内容,特判即可。
对于修改只需要单点修改即可,总复杂度是 \(O(n\log n)\) 的。直接开空间可能紧张,可以用 vector 存储,可以低空飘过时限。

LG7125. [Ynoi2008] rsmemq

考虑一个较为 naive 的想法是对于每一个值 \(x\),以 \(x\) 为中心,\(d\) 为半径的 \(x\) 出现 \(k\) 次的区间是连续的,就是 \(d\in[l,r]\)。同时随着区间的增长,区间众数出现的次数单调不降,也就是在 \([l-1,r]\) 中存在一个数 \(m\) 满足对于 \(d\in[l,m]\) 均满足条件,而 \(d\in[m+1,r]\) 均不满足条件。这启发我们进行二分。而因为每个数出现的次数和恰好为 \(n\),因此这样的区间数量不超过为 \(n\)

可以考虑枚举区间的中心,用分块求区间众数,这样可以做到 \(O(n\sqrt{n}\log n)\),如果块长较优可以做到 \(O(n\sqrt{n\log n})\),然而复杂度太高难以通过。考虑到所有数的出现次数和不超过 \(n\) 是一个良好的性质,我们因此考虑根号分治。

对于所有出现次数 \(>B\) 的数,我们可以暴力枚举所有包含其的区间,那么单次是 \(O(n)\) 的,然而这样的数最多有 \(\dfrac{n}{B}\) 个,于是复杂度为 \(O(\dfrac{n^2}{B})\)

对于所有出现次数 \(\le B\) 的数,按照之前算法的思路实现,依旧考虑二分。但是这次我们首先枚举区间众数的个数 \(k\),然后维护以每个点为左端点时满足区间众数不超过 \(k\) 个的最远右端点,可以用双指针实现做到 \(O(n)\),然后可以通过求出距离中心第 \(k\) 小和第 \(k+1\) 小的对应数的位置求出出事的 \([l,r]\) 区间,正常二分即可。因为区间的个数不超过 \(n\),因此复杂度是 \(O(n\log n)\) 的,不会成为复杂度瓶颈,也就是说这部分的复杂度还是 \(O(nB)\) 的。

因此总复杂度为 \(O(\dfrac{n^2}{B}+nB)\),理论上取 \(B=\sqrt{n}\) 可以做到 \(O(n\sqrt{n})\) 的复杂度,然而根据实际常数来看 \(B\) 小一点更好。

预处理之后考虑求解,考虑对于一个查询区间 \([L,R]\),如果一个中心 \(x\le \dfrac{L+R}{2}\),那么显然我们只需要考虑 \(x-d\) 是否在 \([L,R]\) 的区间内,因为如果 \(x-d\) 在区间内,则 \(x+d\) 一定也在区间内,中心 \(x>\dfrac{L+R}{2}\) 依然同理。于是只需要对于符合条件的 \(x\) 找到有多少个 \(d\) 满足 \(x\pm d\)\([L,R]\) 内即可,不难看出 \(x\pm d\) 是一个区间,因此维护区间加、区间查即可,复杂度是 \(O((n+m)\log n)\) 的,因此复杂度是 \(O(n\sqrt{n}+(n+m)\log n)\) 的。

LG3642. [APIO2016] 烟花表演

不难想到一个将简单的转移方程:令 \(f_{u,x}\) 表示 \(u\) 的子树内所有烟花到 \(u\) 的距离为 \(x\) 的方案数,则有转移方程

\[f_{u,x}=\sum_{v\in\text{son}(u)}\min_{y\le x}f_{v,y}+|w-(x-y)| \]

于是我们有一个 \(O(n(\sum w)^2)\) 的做法,考虑将这个方程看作关于 \(x\) 的函数 \(f_{u}(x)\),我们去考虑转移过程中函数图像的变化。先手玩,发现这个函数是一个分段函数,并且每一段的斜率单调递增,且差值为 \(1\),那么最优取值是斜率为 \(0\) 的段,记为 \([L,R]\)。令 \(F_{v}(x)\) 表示 \(v\)\(u\) 的贡献,则 \(F_v(x)=\min\limits_{y\le x}f_{v}(y)+|w-(x-y)|\)
对于这个函数进行分类讨论:

  1. \(x< L\),此时 \(y=x\) 一定最优,此时 \(F_v(x)=f_v(x)+w\)。否则后半部分的代价最多减少 \(1\),因为观察出的函数的性质,前半部分增加的的代价一定比 \(1\) 大,则总代价不减少。
  2. \(L\le x<L+w\),此时 \(y=L\) 一定最优,此时 \(F_v(x)=f_v(L)+w-(x-L)\)。因为直到 \(L\) 前,前半部分的代价是不变的,而后半部分代价一直减少,之后同理总代价不减少。
  3. \(L+w\le x<R+w\),此时 \(y=x-w\) 一定最优,此时 \(F_v(x)=f_v(x-w)=f_v(L)\)。因为此时不管是前半部分还是后半部分,代价都是最小的。
  4. \(x\ge R+w\),此时 \(y=R\) 一定最优,此时 \(F_v(x)=f_v(R)+(x-R)-w\)。同理可以证明不管怎样调整总代价均不减少。
    汇总一下就是

\[F_v(x)=\left\{\begin{aligned}&f_v(x)+w,&&x<L;\\&f_v(L)+L+w-x,&&L\le x< L+w;\\&f_v(L),&&L+w\le x<R+w;\\&f_v(R)-R-w+x,&&x\ge R+w. \end{aligned}\right. \]

考虑 \(f_v\to F_v\) 的过程是怎样的:将 \(<L\) 的部分向上平移 \(w\) 格,将 \([L,R]\) 的部分向右平移 \(w\) 格,并在这部分两边分别加入斜率为 \(-1,1\) 的线。我们发现这个函数依旧具有原来的性质,我们可以考虑利用 slope trick,维护这个函数图像的拐点,则两个图像相加相当于拐点的合并,利用可并堆可以简单维护这个事情。对于 \(f_v\to F_v\) 的变化,相当于删除 \(L\) 后的所有拐点,接着加入 \(L+w,R+w\) 两个位置的拐点。对于答案的求解,不难考虑到我们已知 \(f_1(0)=\sum w\),利用函数的性质,可以简单算出答案。

LG5284. [十二省联考 2019] 字符串问题

首先问题容易转化成对于每一个 \(A\) 串的前缀 \(B\) 串,从 \(B\)\(A\) 连接一条有向边,然后根据支配关系同样连接有向边,那么所有合法串都可以对应到一条路径上,并且整个串的长度就是路径上经过的所有的 \(A\) 串的长度和。也就是说,问题其实是要在建好的图上求解点权和最长的路径,显然可以用拓扑排序做到 \(O(V+E)\),然而我们连边的条数是 \(O(n^2)\) 的,使用最朴素的判断前缀方法可以做到 \(O(n^3)\)

问题显然出现在建图的边数过于多,考虑到前后缀关系的建图,我们考虑对反串建出后缀自动机后用后缀树优化建图。更具体的,我们考虑确定每一个串在后缀树上对应的点,这个可以通过后缀树上倍增做到 \(O(n\log n)\)。接着对于每一个点上所有串,按照长度排序后,显然长度小的 \(B\) 串一定是长度大的 \(A\) 串的前缀,那么我们对 \(B\) 串建立传递关系后便只需要从每个 \(B\) 串向对应的 \(A\) 串连边即可。同时后缀树上父亲节点的 \(B\) 串也可以转移到当前点,依旧建立传递关系即可。这样边数的复杂度便被优化至 \(O(n)\)。于是总复杂度为 \(O(n\log n)\),可以通过。

CF1458F. Range Diameter Sum

首先这道题有基于点集合并直径的做法,但考虑树上邻域理论的方法可能更好理解一些。

考虑几何意义下邻域和树上邻域的转化。我们称 \(f(u,r)\) 为树上所有距离 \(u\) 不超过 \(r\) 的节点,考虑对于树上的一个点集 \(S\)\(c(S)\) 值能够覆盖它的最小的点集,令 \(m\)\(S\) 中直径的中点,\(d\)\(S\) 中直径的长度,则有 \(c(S)=f(m,\dfrac{d}{2})\)(不难证明 \(m\) 是唯一的)。下文称 \(\text{dis}(u,v)\)\(u,v\) 两点在树上的距离,\(c(S)=f(u_S,r_S)\)\(\text{go}(u,v,k)\) 表示在树上从 \(u\)\(v\)\(k\) 步到达的点。不难看出,点集 \(S\) 的直径即为 \(2r_S\)

接下来考虑合并操作,我们首先判断两个点集 \(S,T\)\(c(S),c(T)\) 是否存在包含关系,不难看出 \(c(T)\subseteq c(S)\) 当且仅当 \(r_T+\text{dis}(u_T,u_S)\le r_S\),这时两者合并的结果为两者中较大的那个。否则,合并的结果为 \(f(\text{go}(u_S,u_T,\dfrac{\text{dis}(u_S,u_T)-r_S+r_T}{2}),\dfrac{\text{dis}(u_S,u_T)+r_S+r_T}{2})\)

声明完基础操作,我们考虑如何求解本题的问题。考虑这种所有区间的问题我们可以考虑分治,对于求解区间 \([l,r]\),找到中点 \(\text{mid}\),对于所有 \(p\in[l,\text{mid}]\) 求出 \(S_p=\{u\mid p\le u\le \text{mid}\}\)\(c(S_p)\),对于所有 \(q\in[\text{mid}+1,r]\) 求出 \(T_q=\{u\mid \text{mid}+1\le u\le q\}\)\(c(T_q)\),我们只需要求出所有 \(p,q\)\(r(c(S_p)\cup c(T_q))\) 的和即可。看起来这个问题依旧很难,但考虑到 \(T_{\text{mid}+1}\subset T_{\text{mid}+2}\subset\cdots\subset T_r\),也就有 \(c(T_{\text{mid}+1})\subseteq c(T_{\text{mid}+2})\subseteq\cdots\subseteq c(T_r)\),因此对于固定的 \(p\),一定可以将 \([\text{mid}+1,r]\) 划分成三段区间使得第一段中的所有 \(c(T)\subset c(S_p)\),第二段中所有的 \(c(T)\not\subset c(S_p)\land c(S_p)\not\subset c(T)\),第三段中所有的 \(c(T)\supset c(S_p)\),并且因为对于 \(S\) 来说依然有上文类似的包含关系,所以这三段的分界线应该单调不减,于是使用双指针即可维护。考虑对于第一段区间的答案即为区间点数乘 \(r_{S_p}\),第三段的答案为其中所有 \(r_T\) 的和,而对第二段区间,考虑合并结果中的 \(r_S+r_T\) 可以通过和上文相同的方法维护,对于 \(\text{dis}(u_S,u_T)\),我们已知所有的 \(u_T\),因此就是需要维护一个点集 \(T\),支持

  • 加入一个点或删除一个点
  • 对于一个点 \(u\) 查询 \(\sum_{v\in T}\text{dis}(u,v)\)

这个内容可以用树剖+线段树做到单次 \(O(\log^2n)\),或是用全局平衡二叉树或点分树做到单次 \(O(\log n)\),于是总复杂度可以做到 \(O(n\log^2n)\)\(O(n\log^3 n)\)

注意因为圆心是直径的中点,因此可能取到两条边的中心处,于是应当对每一条边建立虚点,这样路径长会翻倍,点集的直径长度就不是 \(2r_S\) 而是 \(r_S\) 了。

posted @ 2025-05-28 09:26  DycIsMyName  阅读(22)  评论(0)    收藏  举报