【BZOJ4044】Virus Synthesis(CERC2014)-回文自动机+DP
测试地址:Virus Synthesis
题目大意:一开始你有一个空串,有两种操作:1.往串的前面或后面插入一个A,C,G,T中的字符。2.把整个串翻转后接到这个串前面或后面。问最少需要多少次操作能得到一个给定的字符串?。
做法:本题需要用到回文自动机+DP。
显然一个回文串一定存在一个最优方案,使得最后一次操作是翻转操作。而在最后一次翻转操作后我们得到了一个回文串,最后再手动插入其他字符即可,所以我们只需要算出所有回文子串的最少步数,然后用整个串的长度-回文子串的长度+得到回文子串的最少步数来更新答案即可。
对于一个奇回文串,它的最后一步一定不可能是翻转,所以答案可以记为它本身的长度。那么接下来我们讨论偶回文串。
既然我们知道最后一步一定是翻转,那么我们就只要考虑求翻转前的字符串(也就是该回文串的一半)的最少步数即可,这个步数加就是整个串的答案。
一种显然的想法是,仿照求最终答案的方法递归计算,然而这个时间复杂度……不说了。于是我们考虑目前情况和求最终答案的情况有哪些不同之处,这个不同之处就在于,我们目前的情况处于一个大回文串中,我们需要进一步发掘相关的性质来加快速度。我们发现,因为我们已知该串在一个回文串内,所以该串的一个前缀的答案,就等于一个更短的回文串的答案。
上述性质启发我们,按照回文串长度从小到大的顺序进行DP。而考虑到一个回文串时,我们可以在某个串后加上一个字符再翻转得到这个串,这样答案就等于,当前串去掉头尾后得到的回文串的答案加上。然而我们也可以在一个回文串前插入若干字符再翻转得到这个串,显然这个回文串应该是半串的最长后缀回文串,这样答案就等于这个回文串的答案加上要插入的字符数,再加上。
那么问题的关键就在于如何找到我们需要的回文串的位置。显然应该建一个回文自动机,这样去掉头尾后的回文串就是它在回文自动机上的父亲,而要求长度小于等于半串长的最长后缀回文串,直接在树上跳显然是不行的,会TLE,正确的方法是,因为我们之前已经求了它在回文自动机上父亲的对应位置,那么我们只需要从这个对应位置开始匹配即可,这样复杂度就变成了均摊,于是我们就解决了这一题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int T,n,rt,last,tot;
int ch[100010][4]={0},pre[100010],fail[100010],len[100010],trans[100010];
int f[100010],q[100010];
char s[100010];
int newnode(int l,int fa)
{
pre[++tot]=fa;
len[tot]=l;
memset(ch[tot],0,sizeof(ch[tot]));
return tot;
}
int findfail(int v,int now)
{
while(now-len[v]-1<0||s[now-len[v]-1]!=s[now])
v=fail[v];
return v;
}
void extend(int now,int x)
{
last=findfail(last,now);
if (!ch[last][x])
{
ch[last][x]=newnode(len[last]+2,last);
if (last==2) fail[tot]=1;
else fail[tot]=ch[findfail(fail[last],now)][x];
if (len[tot]<=2) trans[tot]=fail[tot];
else
{
int v=trans[last];
while(s[now-len[v]-1]!=s[now]||((len[v]+2)<<1)>len[tot])
v=fail[v];
trans[tot]=ch[v][x];
}
}
last=ch[last][x];
}
int change(char c)
{
if (c=='A') return 0;
if (c=='C') return 1;
if (c=='G') return 2;
if (c=='T') return 3;
}
void build()
{
tot=last=0;
newnode(0,0),newnode(-1,0);
fail[1]=2,last=1;
for(int i=0;i<n;i++)
extend(i,change(s[i]));
}
void dfs(int v)
{
f[v]=len[v];
for(int i=0;i<=3;i++)
if (ch[v][i]) dfs(ch[v][i]);
}
void dp()
{
int h=1,t=1,ans=n;
q[1]=1;
while(h<=t)
{
int v=q[h++];
if (v==1) f[v]=1;
else f[v]=min(f[pre[v]]+1,(len[v]>>1)-len[trans[v]]+f[trans[v]]+1);
ans=min(ans,n-len[v]+f[v]);
for(int i=0;i<=3;i++)
if (ch[v][i]) q[++t]=ch[v][i];
}
printf("%d\n",ans);
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%s",s);
n=strlen(s);
build();
dfs(2);
dp();
}
return 0;
}