抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题目传送门

题意

\(n+1\) 个字符串 \(S_0,S_1,S_2,\cdots,S_n\)\(S_0\) 是空串,\(S_i\)\(S_{a_i}\) 后加上字符 \(c_i\) 的字符串。\(a_i,c_i\) 都是给定的且 \(0\le a_i< i\)\(c_i\) 是小写字母。

定义字符串 \(T\) 的周期是所有满足 \(T\)\(X\) 重复无穷多次后的字符串的前缀的字符串 \(X\) 中最短的。例如 abcabca 的周期是 abc

\(S_1,S_2,S_3,\cdots,S_n\) 的周期的长度。

\(n\le 500\,000\)

题解

由题目给定的字符串生成方式,可以想到字典树。所有字符串对应字典树上根到某个点的路径。

可以发现,字符串的周期一定是字符串的某个前缀。且这个前缀的长度一定是 \(T[1,m-x]=T[m-x+1,m]\)\(x\) 中的最小值(\(T[l..r]\) 表示 \(T\)\(l\)\(r\) 的子串,\(m\) 表示 \(T\) 的长度)。\(x\) 最小,那么 \(m-x\) 是最大的,所以这是一个求所有前缀=后缀中最长的前缀的形式,那么很自然地能想到 KMP。

在 trie 上做 KMP,不能直接跳 fail,如下图所示的情况就可以卡成 \(O(n^2)\) 的复杂度:

This is a picture without description

因为都是小写字母,所以用 \(go_{i,c}\)(在节点 \(i\) 表示的字符串后加入字符 \(c\) 后最长的前缀=后缀所在的节点) 记下这个跳 fail 的过程即可(有点像 AC 自动机,但不完全一样)。

时间复杂度 \(O(nk)\)\(k\) 是字符集大小,这里为 \(26\)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cstdio>
#define N 500005
int n, dep[N], fail[N], go[N][26];
char a[N];
int edge, hd[N], to[N], pr[N];
void addedge(int u, int v){
to[++edge] = v, pr[edge] = hd[u], hd[u] = edge;
}
void dfs(int u, int fa = -1){
int tmp;
if (~fa){
dep[u] = dep[fa] + 1, fail[u] = go[fa][a[u]];
for (register int i = 0; i < 26; ++i) go[u][i] = go[fail[u]][i];
if (fail[u] == fa) go[u][a[u]] = u;
tmp = go[fa][a[u]], go[fa][a[u]] = u;
}
for (register int i = hd[u]; i; i = pr[i]) dfs(to[i], u);
if (~fa) go[fa][a[u]] = tmp;
}
int main(){
scanf("%d", &n);
for (register int i = 1, fa; i <= n; ++i) scanf("%d", &fa), addedge(fa, i); // 建树
scanf("%s", a + 1);
for (register int i = 1; i <= n; ++i) a[i] -= 'a';
dep[0] = 0, dfs(0);
for (register int i = 1; i <= n; ++i) printf("%d\n", dep[i] - dep[fail[i]]);
}

评论