首页 > 其他分享 >P9997 [Ynoi2000] pmpkmp

P9997 [Ynoi2000] pmpkmp

时间:2024-02-19 21:13:48浏览次数:27  
标签:log 复杂度 Ynoi2000 P9997 修改 pmpkmp 哈希 mathcal 重链

我永远喜欢数据结构。

lxl 大毒瘤!开写到 AC 耗时 \(5\) 天的大毒瘤题,期间学习了神仙@云浅知处的思路。他修改部分的代码也帮助我找到锅 AC,膜拜云浅!

本题是 CF1045J Moonwalk challenge 的加强版,码量超过 \(6\text{ KB}\),作为一名对读者负责人的笔者,我在此必须郑重警告:

\[\color{red}\bf{\text{编写、调试此题可能会引起生理和心理上的极度不适与极端反应,做之前请做好充分的准备。}} \]

题目传送门

  • 给出一棵 \(n\) 个点的树和常数 \(X\)。边上有 \([1,9]\) 的字符。有 \(m\) 次询问,分为两种类型:

    • \(1\texttt{ }x\texttt{ }y\texttt{ }s\),给出长度为 \(X\) 的字符串 \(s\),将 \(x,y\) 两点有向简单路径上的第 \(i\) 条边换成 \(s_i\)。保证这条路径长度为 \(X\)。

    • \(2\texttt{ }x\texttt{ }y\texttt{ }s\),给出长度为 \(X\) 的字符串 \(s\),询问 \(x,y\) 两点有向简单路径上的边上的字符依次拼接形成字符串中,\(s\) 出现了几次。

  • \(n,mX\le 5\times 10^5\)。

  • \(1.5\text{ s / 512 MB}\)。

考虑以 \(1\) 为根对原树进行轻重链剖分。钦定一条边所在重链编号为深度较大的端点所在重链的链顶。则询问 \(2\) 中 \(s\) 出现的位置可以分为这个串对应的边完全在某一条重链内这个串对应的边出现了不同的重链编号

先考虑跨过重链的情况,由于重链只有 \(\mathcal{O}(\log n)\) 条,可以暴力枚举匹配的起点位于哪一条重链,然后取这条重链长度为 \(X - 1\) 的后缀(不足就全部取,下同),与这条重链末端向后走 \(X - 1\) 条边得到的串拼接,得到一个长度为 \(\mathcal{O}(X)\) 的字符串。可以发现一次匹配以当前重链为起点且跨越当前重链当且仅当这个匹配出现在这个字符串上。用哈希实现字符串匹配,记录拼接得到的串每个位置所在重链编号,若一个长度为 \(X\) 的子串起点位于当前重链、终点不在当前重链,且能与 \(s\) 匹配,就计算它的贡献。那么这部分单次时间复杂度就是 \(\mathcal{O}(X\log n)\)。

实现方面,可以考虑按顺序找出并存放路径上的所有重链区间以及方向,每次按照方向从一个端点向另一个端点遍历。还要注意哈希会被卡,我使用了【】生日当乘数、\(998244353\) 当模数。

然后考虑完全在重链内的情况,先对于每个点,若它距离链顶 \(\ge X-1\)(链顶还有一条向上的边),则它向上走 \(X\) 条边得到的字符串仍在重链内,求出从它开始向上走 \(X\) 条边得到的字符串的正反哈希值;否则将其置为 \(0\),这样不会和合法的串冲突。用线段树维护容易做到 \(\mathcal{O}(\log n)\) 时间时间复杂度查询一次某个点的哈希值。

考虑离线对于每种哈希值单独回答询问。对于一次询问,容易跳链查询做到 \(\mathcal{O}(\log^2 n)\) 时间复杂度,但是太不牛了。考虑将路径分成三部分:\(x\) 向上跳的链、\(y\) 向上跳的链和 \(x,y\) 最终到达的同一重链,分别求这三段有多少个完全在重链内的长度为 \(X\) 的串,哈希值等于给定的数。对于前两部分,那些不存在长度为 \(X\) 的完全在重链内的串的点的哈希值一定不会等于给定的数。所以只需要查询路径上有多少个点的哈希值等于给定的数。找到链的上端点(即到达同一重链之前最后所在的点),差分成两条根链计算。这些匹配一定完全包含在路径内,因为它们属于所在重链的某一段,这一段又属于两点间简单路径。两种情况匹配方向分别为向上、向下。

对于第三部分,这条链上某些点向上走 \(X\) 条边可能会超过 \(\text{LCA}(x,y)\),因此若这条链长度不足 \(X\) 则无贡献;否则就从 \(\text{LCA}(x,y)\) 沿着这条链向下走 \(X\) 条边到达一个点。从这个点到链的末尾这一段上的匹配才能够合法。同样这段路径上的点向上走 \(X\) 条边会在同一重链内,所以只需要关系哈希值相等。同样对于根链贡献差分计算,注意一下这一段匹配的方向。由于有修改操作,使用树状数组维护根链贡献,发现一次询问只有 \(\mathcal{O}(1)\) 次单点查询,时间复杂度为 \(\mathcal{O}(\log n)\)。为什么使用树状数组后面会解释。

最后考虑修改。考虑先维护修改后的形态,再计算对根链贡献的影响。那么为了配合跨越重链的计算,需要同时在边权数组上修改以及在线段树上单点修改。由于路径长度为 \(X\),单次修改时间复杂度为 \(\mathcal{O}(X\log n)\)。至于对根链贡献的影响,考虑一条路径上,有 \(\mathcal{O}(\log n)\) 条重链是一个完整的前缀,\(\mathcal{O}(1)\) 条重链是一个区间。对于第一种,设那个重链对应前缀的长度为 \(len\),则只有这 \(len\) 个点向下走 \(X\) 条边到达的点对应的哈希值需要修改;对于第二种,这些重链区间末尾向下走 \(X\) 条边得到的点都可能需要修改。那么一共要修改的点总数为 \(\mathcal{O}(\sum len + X)=\mathcal{O}(X)\) 个,算上修改总时间复杂度为 \(\mathcal{O}(X\log n)\);为了得到这些点,我需要在 \(\mathcal{O}(\log n)\) 条重链上分别遍历 \(X\) 个点,时间复杂度同样为 \(\mathcal{O}(X\log n)\)。

至于每一个被修改的点,假设它的哈希值从 \(H_1\) 变成了 \(H_2\),那么其子树的根链都会经过这些边,即需要将子树内所有点的 \(H_1\) 个数 \(-1\),\(H_2\) 个数 \(+1\),转化为 \(dfn\) 序上的区间。所以前面选择树状数组,可以高效支持区间加、单点查。

同样,初始状态也需要执行 \(\mathcal{O}(n)\) 次这样的操作得到。

这样,我们已经搞清楚了每次修改实际上发生了什么。那么对于这些形如“子树内某种哈希值加定值”的操作,记录下它发生的时刻,结合前面提到的对于每种哈希值单独做,按顺序执行这些操作即可。

更具体地,我们对于每个询问,要求:做完在它之前所有修改后,某个点的根链贡献。然而对于哈希值不同的修改,修改之后并不会对任何一个点根链上当前哈希值个数产生影响。所以只需要对于每种哈希值,将询问和修改按发生时间排序,按顺序执行即可。这样一来,一个修改只会在它对应哈希值的处理中被执行一次,能过保证时间复杂度正确。注意每种哈希值做完后要清空。

那么这题基本就做完了,时间复杂度为 \(\mathcal{O}((n+mX)\log n)\),空间复杂度为 \(\mathcal{O}(n+mX)\)。常数巨大。

AC 记录

AC 代码

标签:log,复杂度,Ynoi2000,P9997,修改,pmpkmp,哈希,mathcal,重链
From: https://www.cnblogs.com/MnZnOIerLzy/p/18021969

相关文章

  • [Ynoi2000] pri
    问题等价于区间翻转区间顺序对数,显然没有复杂度比较好的做法。将操作序列每\(B\)个分一组,对每组进行处理。\(B\)个操作会将序列划分为\(B\)个连续段,在每次操作后都是连续段的一个排列,以及每个连续段内部可能翻转。我们称每个连续段为一个等价类。将值域按\(C\)大小分块......
  • P9995 [Ynoi2000] rspcn 题解
    思路比较典的ODT题目。发现排序是一个非常有性质的操作。它对区间的更改与颜色段均摊差不多。那么我们可以想到用ODT来维护这一整个序列。具体的,区间排序操作可以用ODT维护每次排序产生的段,每段用线段树维护排序后的结果。每次修改就可以进行线段树的分裂与合并。如......