【GDOI2016模拟3.15】基因合成
-
题意:
- 给一个目标串,要求从空串进行最少的操作次数变成目标串,操作有两种:
- 在串的头或尾加入一个字符.
- 把串复制一遍后反向接到串的末尾.
-
因为有回文操作,所以可以想到一些与回文有关的东西.
-
如Manacher,回文树……
-
这里采用强大的回文树.
-
首先注意到目标串可以看作是由一个长度为偶数的回文串在首尾加上若干字符得到的一个串.
-
所以我们可以求出原串中所有的偶回文串,然后再加加减减一下.
-
但为什么长度为奇数的不用讨论呢?
-
这是因为长度为奇数的回文串不可能通过第二个操作得到,所以不管如何,它对答案都没有贡献,它只能通过一个字符一个字符的加得到,尽管它的子串里可能有长度为偶数的回文串,但长度为偶数的回文串无论如何不可能通过第二个操作变为长度为奇数的回文串,所以我们依然可视作它是一个字符一个字符加入的.
-
然后我们继续讨论长度为偶数的回文串.
-
首先它可以由它的父亲状态得来,也就是在它父亲状态的最少操作数上+1得到.
-
当然,它也可以由一个不超过其自身长度一般的最长回文后缀进行一些操作后得来.
-
两者取最小值即为此偶回文串的最少操作数.
-
这实质上就是个在回文树上DP的东西.
-
现在问题还存在的是如何找到一个不超过其自身长度一半的偶回文串.
-
这一点我们可以借鉴回文树求(fail)的过程,我们设一个(trans)表示类似的东西,只不过多一个限制就是不能超过其长度一半.
-
然后每次从一个状态的父亲状态的(trans)开始找起来.
-
同样可以势能分析,时间复杂度显然是(O(n))的.
-
当然,还可以打倍增....不过比较蛋疼。。。
#include <cstdio>
#include <cstring>
#include <iostream>
#define I register int
#define F(i, a, b) for (I i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)
#define mn(a, b) ((a) = (a) < (b) ? (a) : (b))
const int N = 1e5 + 10, M = 26;
using namespace std;
int T; char ch[N];
int cur, lens, las, p, now, ans, len[N], fail[N], son[N][M], f[N], trans[N];
int Node(I x) { len[p] = x; return p ++; }
int getfail(I x) { while (ch[now] ^ ch[now - len[x] - 1]) x = fail[x]; return x; }
void Add(I x) {
cur = getfail(las);
if (!son[cur][x]) {
I t = Node(len[cur] + 2);
fail[t] = son[getfail(fail[cur])][x];
son[cur][x] = t;
}
las = son[cur][x];
if (len[las] & 1)
f[las] = len[las];
else
{
f[las] = f[cur] + 1;
I k = trans[cur];
while (ch[now - len[k] - 1] ^ ch[now] || (len[k] + 2) * 2 > len[las]) k = fail[k];
trans[las] = son[k][x];
mn(f[las], f[trans[las]] + len[las] / 2 - len[trans[las]] + 1);
}
mn(ans, f[las] + lens - len[las]);
}
int main() {
for (scanf("%d", &T); T --; ) {
scanf("%s", ch + 1);
p = 0, lens = strlen(ch + 1);
Node(0), Node(- 1), f[0] = 1;
fail[0] = 1, las = 0, ans = 1e9;
F(i, 1, lens) now = i, Add(ch[i] - 'A');
printf("%d
", ans);
F(i, 0, p + 2) mem(son[i], 0);
}
}