题意
给定 \(n\) 个大小写字母串 \(s_i\)。每次操作可以选择两个没有选过的串 \(s_i,s_j(i\neq j)\),产生 \(\min(|\operatorname{lcp}(s_i,s_j)|,|\operatorname{lcs}(s_i,s_j))|^2\) 的贡献。求能产生的最大贡献。\(1\leq n\leq 2\times 10^5\),\(1\leq \sum|s_i|\leq 3\times 10^5\)。
题解
考虑如果产生的贡献为 \(|\operatorname{lcp}(s_i,s_j)|^2\) 怎么做。贪心,从大到小枚举 \(len\),那么对于所有 LCP 能取到 \(len\) 的字符串对,一定会在当前长度匹配掉。放到 Trie 上,插入一个串时,对于每个经过的节点 \(p\),令 \(cnt_p\gets cnt_p+1\)。按深度从大到小遍历每个点 \(p\),其对答案贡献为 \(\left\lfloor\dfrac{cnt_p}{2}\right\rfloor\times dep_p^2\),然后把 \(p\) 到根的链上的节点的 \(cnt\) 都减去 \(2\left\lfloor\dfrac{cnt_p}{2}\right\rfloor\)。跳过 \(cnt\leq 1\) 的点,暴力更新 \(cnt\) 复杂度就是总体 \(\mathcal{O}(\sum|s_i|)\) 的了。
回到原题,考虑能否重构字符串使得贡献变为 LCP。可以发现,只需要在奇数位置插入原串,偶数位置插入原串的反串,这样贡献就是 \(\left\lfloor\dfrac{|\operatorname{lcp}(s_i,s_j)|}{2}\right\rfloor^2\) 了,套用上述做法即可。时间复杂度为 \(\mathcal{O}(|\Sigma|\times \sum|s_i|)\)。
代码
#include <bits/stdc++.h>using namespace std;#define lowbit(x) ((x) & -(x))
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
const int N = 2e5 + 6, L = 6e5 + 5, C = 53;template<typename T> inline void chk_min(T &x, T y) { x = min(x, y); }
template<typename T> inline void chk_max(T &x, T y) { x = max(x, y); }int n;
int f(char ch) { return ch >= 'a' && ch <= 'z' ? ch - 'a' + 1 : ch - 'A' + 27; }
struct Trie {int tot, mxd, son[L][C], cnt[L], dep[L], fa[L];vector<int> vec[L];void ins(const string &s) {int p = 0;for (char ch : s) {ch = f(ch);if (!son[p][ch]) chk_max(mxd, dep[son[p][ch] = ++tot] = dep[p] + 1), fa[tot] = p;++cnt[p = son[p][ch]];}}ll solve() {ll res = 0;for (int i = 1; i <= tot; ++i) vec[dep[i]].push_back(i);for (int d = mxd; d; --d) for (int x : vec[d]) if (cnt[x] > 1) {res += (ll)(d >> 1) * (d >> 1) * (cnt[x] >> 1);int c = cnt[x] - (cnt[x] & 1);for (int p = x; p; p = fa[p]) cnt[p] -= c;}return res;}
} T;int main() {ios::sync_with_stdio(0), cin.tie(0);cin >> n;for (int i = 1; i <= n; ++i) {string s, t; cin >> s;for (int i = 0; i < s.size(); ++i) t += s[i], t += s[s.size() - 1 - i];T.ins(t);}cout << T.solve();return 0;
}