bzoj 4044 Virus synthesis - 回文自动机 - 动态规划

时间:2023-03-09 14:49:16
bzoj 4044 Virus synthesis - 回文自动机 - 动态规划

题目传送门

  需要高级权限的传送门

题目大意

  要求用两种操作拼出一个长度为$n$的只包含'A','T','G','C'的字符串

  1. 在当前字符串头或字符串结尾添加一个字符
  2. 将当前字符串复制,将复制的串翻转,接在当前字符串末尾或者当前字符串头部、

  问最少的操作次数。

  考虑正常的动态规划时怎么做的。用$f[i][j]$表示拼出给定串的$[l, r]$的最少用的步数。转移显然。

  然而它的时间爆了。空间也爆了。

  仔细观察发现,翻转完最后一个偶回文串后,两端的字符只能暴力插入。

  但是一个串的本质不同的回文串的总数是$O(n)$的级别。因此可以考虑在回文树上动态规划。

  然后用$f[x] + n - len(x)$去更新答案。

  转移显然要分奇偶性讨论。

  1. 如果当前的回文串是奇回文串,那么通过它的最长回文后缀转移,然后暴力补上缺字符,即$f[x] = f[fail[x]] + len(x) - len(fail[x])$。
  2. 如果当前的回文串是偶回文串,那么继续讨论
    • 可以通过它的长度不超过它的一半的最长回文后缀补上一半的残缺字符,然后翻转转移。
    • 如果通过它去掉两端字符的偶回文串在翻转之前再补上一个字符转移。即$f[y] + 1$

  另外说一句,奇回文串的$f[x]$设为$len(x)$也没有任何影响。

  求$half$的做法可以参考NOI某年的动物园。

Code

 /**
* bzoj
* Problem#4044
* Accepted
* Time: 3264ms
* Memory: 4512k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; int cti(char x) {
if (x == 'A') return ;
if (x == 'G') return ;
if (x == 'T') return ;
return ;
} const int N = 1e5 + ; typedef class TrieNode {
public:
int len, f;
TrieNode* ch[];
TrieNode *fail, *half;
}TrieNode; TrieNode pool[N];
TrieNode* top; TrieNode* newnode(int len) {
top->len = len;
memset(top->ch, , sizeof(top->ch));
return top++;
} typedef class PalindromeTree {
public:
char *str;
TrieNode *even, *odd;
TrieNode *last; PalindromeTree() { }
PalindromeTree(char *str):str(str) {
top = pool;
odd = newnode(-);
even = newnode();
odd->fail = odd, even->fail = odd, last = odd;
odd->half = odd, even->half = odd;
} TrieNode* extend(TrieNode* p, int pos) {
while (str[pos] != str[pos - p->len - ]) p = p->fail;
return p;
} void build() {
for (int i = ; str[i]; i++) {
int c = cti(str[i]);
last = extend(last, i);
if (!last->ch[c]) {
TrieNode* pn = newnode(last->len + );
pn->fail = extend(last->fail, i)->ch[c];
if (!pn->fail)
pn->fail = even;
TrieNode* f = last->half;
f = extend(f, i)->ch[c];
if (!f)
f = even;
while (f->len * > pn->len)
f = f->fail;
pn->half = f;
last->ch[c] = pn;
}
last = last->ch[c];
}
} void dp() {
for (TrieNode* p = pool; p < top; p++)
p->f = p->len;
for (TrieNode* p = pool + ; p < top; p++)
if (p->len & )
p->f = p->fail->f + (p->len - p->fail->len);
else {
p->f = min(p->f, p->half->f + ((p->len >> ) - p->half->len) + );
for (int c = ; c < ; c++)
if (p->ch[c])
p->ch[c]->f = min(p->ch[c]->f, p->f + );
}
}
}PalindromeTree; int T;
char str[N];
PalindromeTree pam; inline void init() {
scanf("%s", str + );
} inline void solve() {
pam = PalindromeTree(str);
pam.build();
pam.dp();
int res = , len = strlen(str + );
// for (TrieNode* p = pool; p < top; p++)
// cerr << p - pool << " " << p->fail - pool << " " << p->half - pool << " " << p->len << endl;
for (TrieNode* p = pool; p < top; p++)
res = min(res, len - p->len + p->f);
printf("%d\n", res);
} int main() {
scanf("%d", &T);
while (T--) {
init();
solve();
}
return ;
}