[CERC2014] Virus synthesis 题解
回文自动机好题,首先发现第二种操作的结果肯定是一个回文串,那么理想的结果肯定是一个回文串加上前后接一些杂字符,因为又是和字符串的所有回文子串相关,所以先建出回文自动机。
然后考虑这题中不是所有的子串都是有用的,因为第二种操作的存在,重要的肯定是回文串,而且必须是偶数长度回文串,考虑求出构造每个回文串所需要的最少操作次数,可以想到在回文自动机上跑 dp。
设 \(f_i\) 表示构造回文自动机 \(i\) 节点表示的回文串所需要的最小操作数,首先可以想到初始化 \(f_i=\mathrm{len}(i)\),而且奇数长度字符串的代价已经固定了,因为不可能通过 \(2\) 操作得到,即一个一个用 \(1\) 操作拼,但是这种情况在大部分情况下都是劣的,还有更好的办法,就是利用 \(2\) 操作,可以想到拼出半个串再用 \(2\) 操作完成整个串的构造,且这样必然优于只用 \(1\) 操作拼,所以所有偶数长度回文串构造过程中最后一次必然是 \(2\) 操作,首先可以从回文自动机上父亲处转移,,即 \(f_i=f_{\textrm{fa}_i}+1\),就是说在用 \(2\) 操作翻倍前直接把末尾字符塞进去,这种情况代表着 \(1\) 操作在后面加字符,另一种情况是利用长度不超过一半的后缀回文串前面加字符得到该串的一半,这个可以在求 \(\textrm{fail}\) 指针时顺便求出代表长度不超过当前串一半的回文后缀 \(\textrm{half}\) ,具体实现可以看代码,最后 \(f_i\) 的总动态转移方程 \(f_i=\min\{\operatorname{len}(i),f_{\textrm{fa}_i}+1,f_{\textrm{half}_i}+\operatorname{len}(i)-\operatorname{len}(\textrm{half}_i)\}\),具体实现上第一部分可以初始化赋值(因为奇根的子孙也要赋值),第二部分因为在回文自动机中不存储 \(\textrm{fa}\) 指针,所以可以在父亲处处理,最后在处理第三个部分。
代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
template<typename Ty>
Reimu clear(Ty &x) { Ty _; x.swap(_); }
const int N = 100010;
Marisa get(char c) { return (c > 'A') + (c > 'C') + (c > 'G'); }
struct PAM {
int sz, las, tot;
char s[N];
int len[N], fail[N], half[N], ch[N][4], dp[N];
Reimu init() { memset(ch, tot = 0, sizeof(int) * (sz + 1) << 2); len[sz = las = fail[0] = 1] = -1; }
PAM() { init(); }
Reimu operator+=(char c) {
s[++tot] = c;
int x = las, o = get(c);
while (s[tot - len[x] - 1] ^ c) x = fail[x];
if (!ch[x][o]) {
int y = fail[x];
while (s[tot - len[y] - 1] ^ c) y = fail[y];
fail[++sz] = ch[y][o];
if ((len[ch[x][o] = sz] = len[x] + 2) > 2) {
int z = half[x];
while (s[tot - len[z] - 1] ^ c || len[z] + 2 << 1 > len[sz]) z = fail[z];
half[sz] = ch[z][o];
} else half[sz] = fail[sz];
}
las = ch[x][o];
}
Marisa operator~() {
memcpy(dp + 1, len + 1, sizeof(int) * sz);
int ans = tot;
queue<int> Q;
for (int y: ch[0]) if (y) Q.emplace(y);
while (!Q.empty()) {
int x = Q.front(); Q.pop();
ans = min(ans, tot - len[x] + (dp[x] = min(dp[x], dp[half[x]] + (len[x] >> 1) - len[half[x]] + 1)));
for (int y: ch[x]) if (y) dp[y] = min(dp[y], dp[x] + 1), Q.emplace(y);
}
return ans;
}
} pam;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int T; cin >> T; while (T--) {
pam.init();
string s; cin >> s;
for (char c: s) pam += c;
cout << ~pam << '\n';
}
return 0;
}