[HNOI2019]JOJO
[题目链接]
链接
[思路要点]
题目询问的是当前字符串做 \(\text{kmp}\) 之后的 \(\text{nxt}\) 数组的值的和
首先考虑没有第二种操作的情况
将添加操作看成添加一个字符,这个字符有两个属性,长度和字符。
不难发现,两个子串相匹配,每个子串拆分成开头某一段的后缀 + 中间一堆完整的段 + 结尾某一段的前缀,而题目中一定是某个前缀和某个后缀相匹配,也就是第一个串的开头是完整的段,最后一个串的结尾是完整的段,最后一个串的开头所在段比第一个串可能长,最后一个串结尾所在段比第一个串可能短
由于中间部分完全匹配,所以直接做普通的 \(\text{kmp}\) 即可,开头由于没有修改,直接令全文的第一段能够匹配所有字符和它一样但长度大于等于它的段就处理好了,关键在于结尾串的处理
考虑 \(\text{kmp}\) 的实现过程,实质上是不断跳 \(nxt\) 的过程,由于题目保证每次加入的字符不和之前最后一个字符相同,那么 \(nxt\) 跳在某一段中间时是不可能匹配的,可以减少一些情况。不妨设最后一段的字符是 c
,现在跳到 \(nxt\) 有三处紧跟的字符都是 c
,第一处为 (p1,c,l1)
,第二处为 (p2,c,l2)
,第三处为 (p3,c,l3)
,其中 p
表示 \(nxt\) 的值,也就是跳到的位置,l
表示这一段 c
的长度。假设 p1,l1>l2>l3
。
那么显然,设当前新添加的 c
的长度为 l
,下标为 [1,l]
,那么其 [1,l3]
一段应该和第三处匹配,[l3+1,l2]
一段和第二处匹配,即每次找到一段 c
都覆盖一段位置,并且不能覆盖前面覆盖过的位置(不优),每次的贡献都是一段等差数列。
这样复杂度就是 \(\Theta (m)\) 的。考虑加入第二种操作。首先可以建出操作树并 dfs
完成撤销操作,但是由于 \(\text{kmp}\) 复杂度是均摊 \(\Theta(1)\) 的,不能保证每次操作都很快,那么一个不停返回上一个状态并做一次较慢的操作就能卡掉该算法。实质上,由于总字符数范围大概在 \(1e9\) 左右,暴力 kmp
已经在通过的边缘了,可能剪剪枝就能通过了。
考虑一个叫 \(\text{kmp}\) 自动机的东西,它的本质是把 \(\text{kmp}\) 跳 \(\text{nxt}\) 的过程预处理,由于本题字符集大小很大,用主席树维护。
设 \(f_{i,j,k}\) 表示在串的 \(s_{i-1}\) 位置添加一个字符 \((j,k)\) 后 \(nxt\) 所到达的位置,同理设 \(g_{i,j,k}\) 表示增加的答案。在dfs
的时候,修改 \(f_{i,x,c}\) 的值,并将 \(g_{i,x,1\dots c}\)设置为首项为当前串长度,公差为 \(1\) 的等差数列。dfs
下一层前把 \(f_{i+1}\) 的状态由 \(f_{next[i]+1}\) 继承过来。由于每次加入的是一个等差数列,可以预先减掉下标值,这样变成了区间赋值,统计时再加上每个下标的值即可。
[代码]
// Copyright: lzt
#include
#include
#include
#include
#include
#include