【题目大意】
有 n 次操作,每次往序列 A 末尾插入一个元素,问每次插入后,新产生了多少个本质不同的连续子序列。
注意新产生的子序列必然是当前 A 的后缀序列,因此我们枚举当前 A 的后缀序列 S,然后再枚举 A 的所有除 S 以外的连续子序列,看看有没有相同,如果都没有相同,那么 ans+1。
暴力枚举 A 的所有连续子序列是 30 分做法慢的主要原因,因此我们采用哈希判重。还是枚举当前 A 的后缀序列 S,然后哈希判一下有没有出现过,再把 S加到哈希中。
上述文字中出现了“本质不同”“后缀”等字眼,各位应当联想到,我们要把序列抽象成字符串。
对于 i 位置插入的元素,新产生的子串是 A[1..i]、A[2..i]、A[3..i]……A[i],不考虑本质不同的话贡献就是 i 个。假设存在 j<i,使得 A[j’..j]=A[i’..i],那么对于任意 k∈[i’..i],A[k..i]都是没有贡献的。即如果我们找到了最小的 i’,那么 i’及以后的位置是没贡献的,而 i’以前的位置是有贡献的。如何找这个最小的 i’?我们把A 反序,就会发现,我们实际上是在找 i 开头的后缀与 i’(i<i’)开头的后缀的 LCP(最长公共前缀)。
我们把 A 反序后建立后缀数组,然后从大到小枚举 i,对于位置 i 的插入操作,我们找后缀 i 与所有后缀 j(i<j)的 LCP,然后 n-i+1-LCP 就是 i 位置新产生的贡献。如何找后缀 i 与后缀 j 的 LCP?可以以 SA 为下标弄一棵线段树,每插入一个位置 i,就在 rank[i]的位置打个标记。当我们要找 LCP 的时候,就从 rank[i]开始,往前找到最近的标记点 j,往后找到最近的标记点 k,那么 max(LCP(i,j),LCP(i,k))就是我们要找的 LCP。这个东西可以用任意一种 log 的数据结构维护,比如线段树、树状数组什么的。
既然要找后缀 i 与所有后缀 j(i<j)的 LCP,我们可以直接用后缀自动机,该后缀与之前后缀的最大 LCP 即为其 father 的 maxlen,那么该后缀的贡献应为该点的 maxlen 减 father 的 maxlen。
时间复杂度远小于 O(n log n)
有 n 次操作,每次往序列 A 末尾插入一个元素,问每次插入后,新产生了多少个本质不同的连续子序列。
注意新产生的子序列必然是当前 A 的后缀序列,因此我们枚举当前 A 的后缀序列 S,然后再枚举 A 的所有除 S 以外的连续子序列,看看有没有相同,如果都没有相同,那么 ans+1。
时间复杂度 O(n^4)
暴力枚举 A 的所有连续子序列是 30 分做法慢的主要原因,因此我们采用哈希判重。还是枚举当前 A 的后缀序列 S,然后哈希判一下有没有出现过,再把 S加到哈希中。
如果把哈希看成是 O(1)的话,那么时间复杂度是 O(n^2)
上述文字中出现了“本质不同”“后缀”等字眼,各位应当联想到,我们要把序列抽象成字符串。
对于 i 位置插入的元素,新产生的子串是 A[1..i]、A[2..i]、A[3..i]……A[i],不考虑本质不同的话贡献就是 i 个。假设存在 j<i,使得 A[j’..j]=A[i’..i],那么对于任意 k∈[i’..i],A[k..i]都是没有贡献的。即如果我们找到了最小的 i’,那么 i’及以后的位置是没贡献的,而 i’以前的位置是有贡献的。如何找这个最小的 i’?我们把A 反序,就会发现,我们实际上是在找 i 开头的后缀与 i’(i<i’)开头的后缀的 LCP(最长公共前缀)。
我们把 A 反序后建立后缀数组,然后从大到小枚举 i,对于位置 i 的插入操作,我们找后缀 i 与所有后缀 j(i<j)的 LCP,然后 n-i+1-LCP 就是 i 位置新产生的贡献。如何找后缀 i 与后缀 j 的 LCP?可以以 SA 为下标弄一棵线段树,每插入一个位置 i,就在 rank[i]的位置打个标记。当我们要找 LCP 的时候,就从 rank[i]开始,往前找到最近的标记点 j,往后找到最近的标记点 k,那么 max(LCP(i,j),LCP(i,k))就是我们要找的 LCP。这个东西可以用任意一种 log 的数据结构维护,比如线段树、树状数组什么的。
时间复杂度 O(nlogn)
既然要找后缀 i 与所有后缀 j(i<j)的 LCP,我们可以直接用后缀自动机,该后缀与之前后缀的最大 LCP 即为其 father 的 maxlen,那么该后缀的贡献应为该点的 maxlen 减 father 的 maxlen。
时间复杂度远小于 O(n log n)