CCPC2021黑龙江省赛E. Elastic Search (fail树,trie,dp)
https://codeforces.com/gym/103107/problem/E
题意: 给出n个字符串si,求最长字符串的序列S, S满足 对所有的i从1到n-1, si 是si+1的子串。
思路: si的子串就是si的一些前缀的后缀(经典套路),也就是在字典树上si的祖宗的fail树的祖宗
dp数组f[u]表示节点u对应字符串为起点的最长字符串序列长度。 u在字典树上的父亲fa[u],在fail树上的父亲fail[u], ed[i] 节点i表示的字符串的个数
$f[u] = max( f[fa[u]], f[fail[u]] ) + ed[u] $
时间复杂度On
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false) ,cin.tie(0), cout.tie(0);
//#pragma GCC optimize(3,"Ofast","inline")
#define ll long long
//#define int long long
const int N = 5e5 + 6;
const int M = 2e6 + 6;
const ll P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000003;
const double PI = acos(-1.0);
namespace AC {
int tr[N][26], tot;
int fail[N], sz[N];
void insert( char *s ) {
int u = 0;
for ( int i = 1; s[i]; ++ i ) {
int ch = s[i] - 'a';
if(!tr[u][ch]) tr[u][ch] = ++ tot;
u = tr[u][ch];
}
}
queue <int> q;
void build() {
for ( int i = 0; i < 26; ++ i ) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()) {
int u = q.front(); q.pop();
for ( int i = 0; i < 26; ++ i ) {
if(tr[u][i]) fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
}
namespace Trie {
int tr[N][26], tot, ed[N], fa[N];
void insert( char *s ) {
int u = 0;
for ( int i = 1; s[i]; ++ i ) {
int ch = s[i] - 'a';
if(!tr[u][ch]) tr[u][ch] = ++ tot;
fa[tr[u][ch]] = u;
u = tr[u][ch];
}
++ ed[u];
}
int f[N], ans = 1;
void bfs() {
queue<int> q;
for ( int i = 0; i < 26; ++ i ) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()) {
int u = q.front(); q.pop();
f[u] = max( f[fa[u]], f[AC::fail[u]] ) + ed[u];
ans = max( ans, f[u] );
for ( int i = 0; i < 26; ++ i ) {
int v = tr[u][i];
if(v) {
q.push(v);
}
}
}
}
}
char s[N];
int main () {
int n; cin >> n;
for ( int i = 1; i <= n; ++ i ) {
scanf("%s", s + 1);
Trie::insert(s); AC::insert(s);
}
AC::build();
Trie::bfs();
cout << Trie::ans << '\n';
return 0;
}