Solution
首先只考慮迴文串的答案;我們重點考慮的是偶迴文串
結論:對於偶迴文串 \(u\),從其最長的長度小於等於他的一半的迴文字尾,或其父親轉移過來,一定是最優的
證明:
設 \(u\) 的一個迴文子串為 \(v\)(不是父親),你要讓 \(v\to u\) 的轉移最優
首先 \(v\) 不能跨過 \(u\) 的中點,因為此時“從父親轉移過來”一定不會更劣
其次 \(v\) 不能位於 \(u\) 的中間(非前字尾),因為這種情況 \(u\) 的祖先已經考慮到了
證完了
於是轉移即可;對於奇迴文串,他只能從其父親或 fail 轉移而來,證明是類似的。
做完了。
Code
#include <bits/stdc++.h>
using namespace std;
#define rep(i, j, k) for (int i = (j); i <= (k); ++i)
#define reo(i, j, k) for (int i = (j); i >= (k); --i)
typedef long long ll;
const int N = 1e5 + 10;
string s;
int n;
int cur, last, sz, len[N], fail[N], nxt[N][4], f[N], half[N];
void init() {
while (sz >= 0) {
len[sz] = fail[sz] = f[sz] = half[sz] = 0;
rep(i, 0, 3) nxt[sz][i] = 0;
--sz;
}
cur = last = 0, sz = 1, len[1] = -1, fail[0] = 1;
}
int getfail(int u, int i) {
while (i - len[u] - 1 < 0 || s[i - len[u] - 1] != s[i]) u = fail[u];
return u;
}
void Solve() {
cin >> s, n = s.length(), init();
int ans = 114514;
auto Get = [&](char c) {
if (c == 'A') return 0;
if (c == 'G') return 1;
if (c == 'T') return 2;
if (c == 'C') return 3;
return 0;
};
rep(i, 0, n - 1) {
int p = getfail(last, i), ch = Get(s[i]);
if (!nxt[p][ch]) {
cur = ++sz;
fail[cur] = nxt[getfail(fail[p], i)][ch];
nxt[p][ch] = cur;
len[cur] = len[p] + 2;
if (len[cur] & 1) {
f[cur] = min(f[p] + 2, f[fail[cur]] + len[cur] - len[fail[cur]]);
} else {
int q = getfail(half[p], i);
while (len[q] + 2 > len[cur] / 2) q = getfail(fail[q], i);
if (len[cur] > 2 && (q || s[i] == s[i - 1])) q = nxt[q][ch];
else q = 0;
half[cur] = q;
if (p) {
f[cur] = min(f[p] + 1, f[half[cur]] + len[cur] / 2 - len[half[cur]] + 1);
} else {
f[cur] = 2;
}
}
ans = min(ans, n - len[cur] + f[cur]);
}
last = nxt[p][ch];
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
cin >> t;
while (t--) Solve();
return 0;
}