ix6hio1n
jzc 大哥选的题太有水平了(但是好难啊),记一下。
QOJ 13457. 「2020-2021 集训队作业」GDSOI2019 novel 加强版
要对所有 \(i, j\) 求出 \(h(s_i[1 : j])\),固定 \(i\) 时 \([1 : j]\) 是前缀有包含关系,于是可以先算 \(H(i, j) = \max\limits_{1\le l\le j} \frac{g(S_i[l : j])}{j - l + 1}\)(相当于是固定了右端点)再取前缀 \(\max\)。
\(H(i, j)\) 这个式子长得很像斜率的式子,转化一下这就是 \((j + 1, 0)\) 与 \((l, g(S_i[l : j]))\) 连线的最小斜率的相反数,而这显然只需要保留 \((l, g(S_i[l : j]))\) 所构成的上凸包。
那就可以考虑维护这个凸包了,固定 \(i\) 从小到大扫描 \(j\),分析 \(g(S_i[l : j])\) 的变化。考虑 \(j - 1\to j\),\(g(S_i[l : j])\) 的变化量就是 \(S_i[l : j]\) 在 ACAM 上走到的节点的权值(这里的权值要包含 \(\operatorname{fail}\) 树祖先的权值)。
记 \(p\) 为 \(S_i[1 : j]\) 在 ACAM 的位置,\(S_i[l : j]\) 因为是 \(S_i[1 : j]\) 的后缀,所以一定是 \(p\) 或者 \(p\) 在 \(\operatorname{fail}\) 树上的祖先,具体来说若 \(l = 1\) 则就在 \(p\),若 \(j - l + 1 \ge \operatorname{dep}(\operatorname{fail}(p))\) 则在 \(\operatorname{fail}(p)\),否则会继续往上跳。
看起来根据 \(l\) 的值会是分段函数的形式,凸包就不好维护了。但是考虑若 \(j - l + 1 < \operatorname{dep}(\operatorname{fail}(p))\),那么 \(S_i[l : j]\) 的信息在 \(\operatorname{fail}(p)\) 处就已经考虑过了。这说明可以把凸包的 \(l\) 的范围卡到 \(j - l + 1\ge \operatorname{dep}(\operatorname{fail}(p))\),之后只需要在 \(\operatorname{fail}\) 树上也求一个前缀 \(\max\)。同时因为 \(l = 1\) 也不太一样,所以也认为 \(l > 1\),之后需要再与 \(l = 1\) 取一个 \(\max\)(这是一个前缀,所以可以在 ACAM 上可以直接统计出来)。
剩下的 \(l\) 就满足 \(S_i[l : j]\) 在 ACAM 上的位置为 \(\operatorname{fail}(p)\) 了,增加量都是一个值,是一个凸包平移,用一个 tag 记录一下就好了。
剩下的还有一个问题是 \(j - 1\to j\) 时 \(l\) 的范围是不会变小的,但是可能会变大(因为 \(\operatorname{dep}(\operatorname{fail}(p))\) 至多 \(+1\)),也就是会加入新的 \(l\),但这部分的权值都是没有计算的,于是要考虑如何计算这些值。
正着计算需要快速算出 \(s_i[l : j]\) 在 ACAM 上的位置,这是做不了的。于是考虑反着算,因为最大的 \(l\) 满足 \(j - l + 1 = \operatorname{dep}(\operatorname{fail}(p))\),所以 \(s_i[l : j]\) 其实就对应着 \(\operatorname{fail}(p)\),这个信息是可以直接用的。然后考虑不断减小 \(l\) 的值,建出所有串的反串的 ACAM,这样就可以往前加字符并算出新的匹配的贡献了。
这也引出了一个新的问题,如何定位正串节点 \(p\) 在反串上匹配的位置。考虑正串从后往前删字符反串就是从前往后删字符,而从前往后删字符是好定位的,若当前节点的 \(\operatorname{dep}\) 已经大于串长则跳 \(\operatorname{fail}\) 即可。所以先得到 \(s_i\) 整个串在反串的位置,再往后删字符倒着递推出每个前缀在反串上的位置即可。
时间复杂度 \(\mathcal{O}(\sum\limits |s_i|\times |\Sigma|)\)。
QOJ 2867. 「SDOI 2017」文本校正
因为只会分成 \(3\) 段,考虑分讨 \(3\) 段的分段情况。
首先考虑 \(123, 231, 321\) 这 \(3\) 种,这 \(3\) 种是 \(123\) 的循环同构,所以只需要把 \(a\) 倍长去匹配 \(b\) 即可。
然后考虑 \(132, 213\) 这 \(2\) 种,这 \(2\) 种的共性是前缀后缀必有一个固定了,且这 \(2\) 种翻转后是等价的,所以只考虑 \(213\) 这种情况。
从固定的 \(3\) 入手,找到最小的 \(r\) 满足 \(a[r + 1 : n] = b[r + 1 : n]\),这样只需要前两段的长度 \(\ge r\) 就是合法的。
记第一段长度为 \(x\),第二段长度为 \(y\),要满足的条件如下:
- \(x + y\ge r\)。
- \(\operatorname{lcp}(a[x + 1: n], b)\ge y\)。
- \(\operatorname{lcp}(b[y + 1: n], a)\ge x\)。
预处理出 \(z\) 函数和 \(\operatorname{lcp}\) 后就是一个二维数点,可以做到 \(\mathcal{O}(n\log n)\)。
考虑不去枚举 \(x, y\),转而枚举 \(x + y\),对 \(a\) 跑一个 KMP 得到 \(\operatorname{fail}\) 树。当 \(x + y = z\) 时,可能的 \(x\) 必定是 \(b[1 : z]\) 与 \(a\) 匹配上的最长前缀 \(a[1 : p]\) 的 \(p\) 或者 \(p\) 在 \(\operatorname{fail}\) 树上的祖先。
再考虑 \(x + y = z\) 的限制,因为 \(y\le \operatorname{lcp}(a[x + 1 : n], b)\),所以存在 \(x + \operatorname{lcp}(a[x + 1 : n], b)\ge z\) 即可。贪心的一定是取值最大的 \(x\),在 \(\operatorname{fail}\) 树上做个前缀 \(\max\) 即可。
最后考虑 \(321\) 这种情况。
考虑如何刻画第一个 \(3\) 的匹配,假设长度为 \(l\) 也即 \(a[1 : l] = b[n - l + 1 : n]\),那就有 \(a_1 = b_{n - l + 1}, \cdots, a_{l} = b_{n}\)。一个巧妙的想法是考虑如下整合两个串:\(s = a_1 b_n a_2 b_{n - 1} \cdots a_n b_1\)。此时会发现若 \(l\) 合法则 \(s[1 : 2l]\) 是一个回文串。
于是这个题被转换为了串 \(s\) 能否被分成 \(3\) 个偶数长度的回文串。
跑一个 manacher,就可以 \(\mathcal{O}(1)\) 判断一个区间是否回文了(算出中心点与半径比较)。
一个很厉害的结论是一个串 \(s\) 若能被拆分成两个回文串,则一定存在一个拆分方案使得靠前的串是极长的前缀回文串或靠后的串是极长的后缀回文串,证明我没懂,先贴个别人的链接:https://www.luogu.com.cn/article/57x0bsu6 。
于是考虑枚举第一个串的分割点 \(a[1 : i - 1]\),后面的部分就只需要维护前缀极长回文串和后缀极长回文串了再 check 了。
倒着扫描 \(i\),后缀的极长回文串是简单的,只需要判断 \(a[i : n]\) 是不是回文串。
前缀极长回文串考虑若 \(i\) 处的极长回文串是 \(a[i : j]\),\(i + 1\) 处的极长回文串至少是 \(a[i + 1 : j - 1]\),这说明 \(i\) 处极长回文串的右端点 \(\le\) \(i + 1\) 处极长回文串右端点 \(+ 1\),于是可以记 \(r\) 表示 \(i\) 处极长回文串为 \(a[i : r]\),每次 \(i\gets i - 1\) 时 \(r\gets \min(r + 1, n)\),若 \(a[i : r]\) 不为回文串则不断 \(r\gets r - 1\),复杂度是均摊线性的。
于是整个题的复杂度就做到了 \(\mathcal{O}(n)\)。
UOJ 752. 【UNR #6】Border 的第五种求法
首先一个串的价值是可以直接在 SAM 上求出的。
考虑刻画出 \([l, r]\) 的所有 border,暴力展开 border 的表达式:\(\{s[l : i] \mid l\le i\le r\} \cap \{s[i : r] \mid 1\le i\le r\}\)。后面一个集合就是 SAM 上 \(s[1 : r]\) 所在点及其 \(\operatorname{fail}\) 树上祖先,是简单的,于是着重考虑刻画第一个集合。
这可以看作是在 SAM 上不断走 \(s_i\) 的转移边得到的节点集合,但问题是转移边构成的是一个 DAG,也就是现在需要刻画出 DAG 上的一条路径。
仿照树链剖分的方法,尝试把 DAG 剖成若干条重链。
一个初步的想法是记 \(g_u\) 为以 \(u\) 开头的路径数,转移为 \(g_u = 1 + \sum_{(u, v)} g_v\),然后选取 \(g_v\) 最大的 \(v\) 作为 \(v\)。但是 DAG 并不能保证每个点入度为 \(1\),所以连出来的是一个根向森林。
于是再考虑记 \(f_u\) 表示以 \(u\) 结尾的路径数,转移为 \(f_{\varnothing} = 1, f_v = \sum_{(u, v)} f_u\)。且为了保证为链需要入点出点唯一,令 \(2f_u > f_v, 2g_v > g_u\) 时 \((u, v)\) 为重边。
考虑 DAG 路径上的轻边 \((u, v)\),其必定满足 \(2f_u\le f_v\) 或 \(2g_v\le g_u\),所以必有 \(f\) 翻倍或 \(g\) 减半,而走重边 \(f\) 不降 \(g\) 不升,所以经过的轻边数量是 \(\mathcal{O}(\log \max f + \log \max g)\) 的。考虑到 SAM 转移边建出来的 DAG 满足每一条路径都对应 \(s\) 的一个子串,所以 \(\max f, \max g\) 都是 \(\mathcal{O}(n^2)\) 的,故任意一条 DAG 路径经过的轻边数量是 \(\mathcal{O}(\log n)\) 的,于是经过的重链数量是 \(\mathcal{O}(\log n)\) 的。
于是可以按照重链重编号,让每条链的元素编号按照链的顺序连续,离线后在 \(\operatorname{fail}\) 树上 dfs(用来刻画第二个集合),就只剩下 \(\mathcal{O}(q\log n)\) 个区间和的查询了。
前面还忽略了一个问题是把 \(s[l : i](l\le i\le r)\) 定位到重链的每个区间上。考虑得到重链的时候是知道重链上相邻两个点 \((u, v)\) 转移边的字符 \(c\) 的,就可以对于一个重链把其转移边代表的字符串记下来 Hash。
在遍历重链的过程中维护当前所在的节点 \(u\) 和 \(u\) 对应的区间 \(s[l : i]\),二分得到从 \(u\) 往下重链对应的字符串和 \(s[i + 1 : r]\) 的 LCP,记为 \(\operatorname{len}\),那么 \([u, u + \operatorname{len}]\) 就是这个重链定位到的区间。然后 \(u\gets u + \operatorname{len}, i \gets i + \operatorname{len}\),再跳一次轻边 \(u\gets \operatorname{trans}(u, s_{i + 1}), i\gets i + 1\) 继续往下处理即可。
时间复杂度 \(\mathcal{O}(q\log^2 n + n \log n + n |\Sigma|)\)。
浙公网安备 33010602011771号