题目地址:
https://www.acwing.com/problem/content/2574/
魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示。例如可以将魔咒字符 1 , 2 1, 2 1,2拼凑起来形成一个魔咒串 [ 1 , 2 ] [1,2] [1,2]。一个魔咒串 S S S的非空子串被称为魔咒串 S S S的生成魔咒。例如 S = [ 1 , 2 , 1 ] S=[1,2,1] S=[1,2,1]时,它的生成魔咒有 [ 1 ] , [ 2 ] , [ 1 , 2 ] , [ 2 , 1 ] , [ 1 , 2 , 1 ] [1],[2],[1,2],[2,1],[1,2,1] [1],[2],[1,2],[2,1],[1,2,1]五种。 S = [ 1 , 1 , 1 ] S=[1,1,1] S=[1,1,1]时,它的生成魔咒有 [ 1 ] , [ 1 , 1 ] , [ 1 , 1 , 1 ] [1], [1,1],[1,1,1] [1],[1,1],[1,1,1]三种。最初 S S S为空串。共进行 n n n次操作,每次操作是在 S S S的结尾加入一个魔咒字符。每次操作后都需要求出,当前的魔咒串 S S S共有多少种生成魔咒。
输入格式:
第一行一个整数
n
n
n。
第二行
n
n
n个数,第
i
i
i个数表示第
i
i
i次操作加入的魔咒字符。
输出格式:
输出
n
n
n行,每行一个数。第
i
i
i行的数表示第
i
i
i次操作后
S
S
S的生成魔咒数量。
数据范围:
1
≤
n
≤
1
0
5
1≤n≤10^5
1≤n≤105
用来表示魔咒字符的数字
x
x
x满足
1
≤
x
≤
1
0
9
1≤x≤10^9
1≤x≤109
相当于是动态求一个字符串有多少个本质不同的子串。求一个字符串有多少个本质不同子串可以用后缀数组来做,参考https://blog.csdn.net/qq_46105170/article/details/125131642,设字符串长 n n n,高度数组为 h h h,则本质不同子串个数为 n ( n + 1 ) 2 − ∑ i = 2 n h [ i ] \frac{n(n+1)}{2}-\sum_{i=2}^n h[i] 2n(n+1)−∑i=2nh[i]。但是这道题是在后面添加字符,这样一来每个后缀都会发生变化,不好求,可以将整个字符串翻转,这样变成了每次从开头添加字符,之前的后缀都不变,只是添加了一个新后缀。但是,考虑添加一个新后缀仍然不方便更新 h h h,我们可以反过来做,直接将最后所得的字符串 s s s求出,然后每次删除首字母,这样就变成每次删除一个后缀,由于有 r k rk rk数组,这个后缀的排名是知道的,再用一个双链表维护每个后缀字典序小 1 1 1的后缀是哪一个,例如后缀 x x x的字典序的后一个后缀是 d [ x ] d[x] d[x],这样更新 h h h的时候,由 l c p lcp lcp的性质, h [ d [ r k [ x ] ] ] h[d[rk[x]]] h[d[rk[x]]]更新为 min { h [ r k [ x ] ] , h [ d [ r k [ x ] ] ] } \min\{h[rk[x]],h[d[rk[x]]]\} min{h[rk[x]],h[d[rk[x]]]}即可,同时再维护子串个数表达式的第一项和第二项。代码如下:
#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int s[N];
int sa[N], rk[N], y[N], c[N], he[N];
int u[N], d[N];
long res[N];
// 需要离散化
int get(int x) {
static unordered_map<int, int> mp;
if (!mp.count(x)) mp[x] = ++m;
return mp[x];
}
void get_sa() {
for (int i = 1; i <= n; i++) c[rk[i] = s[i]]++;
for (int i = 2; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[rk[i]]--] = i;
for (int k = 1;; k <<= 1) {
int num = 0;
for (int i = n - k + 1; i <= n; i++) y[++num] = i;
for (int i = 1; i <= n; i++) if (sa[i] > k) y[++num] = sa[i] - k;
for (int i = 1; i <= m; i++) c[i] = 0;
for (int i = 1; i <= n; i++) c[rk[i]]++;
for (int i = 2; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[rk[y[i]]]--] = y[i];
swap(rk, y);
rk[sa[1]] = num = 1;
for (int i = 2; i <= n; i++)
rk[sa[i]] = y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? num : ++num;
if (num == n) break;
m = num;
}
}
void get_height() {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 1) continue;
if (k) k--;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
he[rk[i]] = k;
}
}
int main() {
scanf("%d", &n);
for (int i = n; i; i--) scanf("%d", &s[i]), s[i] = get(s[i]);
get_sa();
get_height();
long ans = 0;
// 按排名枚举后缀
for (int i = 1; i <= n; i++) {
// 右边是排名第i的后缀有多少个前缀之前没算过
ans += n - sa[i] + 1 - he[i];
// 构造出双链表
u[i] = i - 1, d[i] = i + 1;
}
// 开两个哨兵
d[0] = 1, u[n + 1] = n;
for (int i = 1; i <= n; i++) {
res[i] = ans;
// 每次删掉第i个字符开始的后缀
int k = rk[i], j = d[k];
// 清空掉排名第k后缀和排名第j后缀的贡献
ans -= n - sa[k] + 1 - he[k];
ans -= n - sa[j] + 1 - he[j];
// 更新高度数组
he[j] = min(he[j], he[k]);
// 将排名第j的后缀的贡献加回来
ans += n - sa[j] + 1 - he[j];
// 双链表中删掉第i个字符开始的后缀
d[u[k]] = d[k], u[d[k]] = u[k];
}
// 要逆序输出答案
for (int i = n; i; i--) printf("%ld\n", res[i]);
}
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)。