题目链接:
https://www.luogu.com.cn/problem/P7537
题意:
记字符串 \(A\) 和 \(B\) 的最长公共后缀为 \(LCS(A, B)\)。
当 \(LCA(A, B) >= max(|A|, |B|) - 1\) 时,认为这两个字符串是押韵的。
给定 \(n\) 个字符串(两两不相同),要求从其中组合出一个长度最长的字符串序列,使得序列中相邻的两个字符串押韵。
思路:
容易想到,将字符串翻转一下,后缀就变成了前缀,然后将字符串挂到字典树上。
如果要满足两个字符串的最大公共前缀长度 >= 最长的字符串的长度 - 1,只有两种情况。
设字符串为 \(S\),字符为 \(C\)。
那么只可能是 \(S + C_1\) 和 \(S + C_2\) 或者 \(S\) 和 \(S + C\)。
设 \(dp[u]\) 为以字符 \(u\) 为结尾的字符串中,押韵字符串的最大数量。
对于一个字符串,要找最大押韵序列,它可以从自己的子节点转移过来,但是不能所有的节点都转移过来,只能有两个,如下图这种方式。
最中间的字符串找到的最大的押韵序列,就是它子节点中最大的两个孩子的数量之和。
同时,它也可以用剩余孩子,但是不能用它们的孩子。
比如:
a
ab
abc
abcd
ac
acd
acde
ad
ade
以上为翻转了之后的字符串,答案为 8。
所以对于 \(u\),它的最大值就是最大的两个孩子的数量 + 自己 + 剩余有的孩子的数量。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 3e6 + 10;
char s[N];
LL ans;
struct Trie{
LL trie[N][26], cnt[N], dp[N], idx = 0;
void insert(char s[]){
LL u = 0, len = strlen(s);
for (int i = len - 1; i >= 0; i -- ){
LL v = s[i] - 'a';
if (!trie[u][v]) trie[u][v] = ++ idx;
u = trie[u][v];
}
cnt[u] ++ ;
}
void dfs(LL u){
LL mx1 = 0, mx2 = 0, count = 0;
for (int i = 0; i < 26; i ++ ){
LL v = trie[u][i];
if (!v) continue;
dfs(v);
count += cnt[v];
if (dp[v] > mx1){
mx2 = mx1;
mx1 = dp[v];
}
else if (dp[v] > mx2){
mx2 = dp[v];
}
}
if (cnt[u])
dp[u] = mx1 + max(count, 1LL);
ans = max(ans, mx1 + mx2 + max(0LL, count - 2) + cnt[u]);
}
}trie;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL n;
cin >> n;
for (int i = 0; i < n; i ++ ){
cin >> s;
trie.insert(s);
}
trie.dfs(0);
cout << ans << "\n";
return 0;
}