题意
有 n ( n ≤ 1 0 5 ) n(n\le 10^5) n(n≤105) 个字符串, ∑ l ≤ 1 0 5 \sum l\le 10^5 ∑l≤105 。
现在要将所有字符串的所有前缀分组,保证每组内的字符串不能有包含关系。
求最小的组数。
思路
首先考虑建出 AC 自动机,每个前缀就是 AC 自动机上的一个节点。我考虑不出来,但是好像处理字符串也就那么几个算法,挑一个用就好了。
然后考虑子串在 AC 自动机上的表示,即 t t t 是 s s s 的子串的话,那么一定有一条路径,以 AC 自动机上边的反向边和 fail 指针为单向边,从 s s s 所在的点走到 t t t 所在的点。
上面那个略微理解一下就好了, 树边的反向边指向的是原串的前缀, fail 指针指向的是原串的后缀。
那么想要一个没有包含关系的前缀集合,就是要找一些互不连通的点组成的点集。
那么想要最小化组数,就要最大化每次能够取出的点数。那么就把图建出来,每次把入度等于 0 的点取出来。暴力计算就好了,因为长度之和也就是前缀数量很少。
注意
AC 自动机的 fail 边一定要等整棵 trie 树建完之后才建。因为不是单模匹配,所以不能像 KMP 一样加一个字符就连一条 fail 。
代码
// 10.29 T3
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, S = 26;
int n, ans;
char s[N];
int ch[N][S], fa[N], fail[N], dgr[N], cnt[N], ncnt;
queue<int> que, que1;
void insert(int n, char *s){
int now = 1, tr;
for (int i = 1; i <= n; ++ i){
tr = s[i]-'a';
if (!ch[now][tr]){
ch[now][tr] = ++ncnt;
fa[ncnt] = now;
dgr[now]++;
}
now = ch[now][tr];
cnt[now]++;
}
}
void build()
{
fail[1] = 0;
for (int i = 1; i <= ncnt; ++ i)
for (int j = 0; j < S; ++ j)
if (ch[i][j]){
for (int k = fail[i]; k; k = fail[k])
if (ch[k][j]){
fail[ch[i][j]] = ch[k][j];
break;
}
if (fail[ch[i][j]] == 0) fail[ch[i][j]] = 1;
dgr[fail[ch[i][j]]]++;
}
}
int main()
{
scanf("%d", &n);
ncnt = 1;
memset(dgr, 0, sizeof dgr); dgr[1] = 1;
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++ i){
scanf("%s", s+1);
insert(strlen(s+1), s);
}
build();
for (int i = 1; i <= ncnt; ++ i)
if (dgr[i] == 0)
que.push(i);
ans = 0;
while (!que.empty()){
ans++;
while (!que.empty()){
int u = que.front(); que.pop();
cnt[u]--;
if (cnt[u]) que1.push(u);
else{
dgr[fail[u]]--;
if (dgr[fail[u]] == 0) que1.push(fail[u]);
dgr[fa[u]]--;
if (dgr[fa[u]] == 0) que1.push(fa[u]);
}
}
while (!que1.empty()){
que.push(que1.front()); que1.pop();
}
}
printf("%d\n", ans);
return 0;
}