【BZOJ4044】Virus Synthesis(CERC2014)-回文自动机+DP

测试地址:Virus Synthesis
题目大意:一开始你有一个空串,有两种操作:1.往串的前面或后面插入一个A,C,G,T中的字符。2.把整个串翻转后接到这个串前面或后面。问最少需要多少次操作能得到一个给定的字符串?len105
做法:本题需要用到回文自动机+DP。
显然一个回文串一定存在一个最优方案,使得最后一次操作是翻转操作。而在最后一次翻转操作后我们得到了一个回文串,最后再手动插入其他字符即可,所以我们只需要算出所有回文子串的最少步数,然后用整个串的长度-回文子串的长度+得到回文子串的最少步数来更新答案即可。
对于一个奇回文串,它的最后一步一定不可能是翻转,所以答案可以记为它本身的长度。那么接下来我们讨论偶回文串。
既然我们知道最后一步一定是翻转,那么我们就只要考虑求翻转前的字符串(也就是该回文串的一半)的最少步数即可,这个步数加1就是整个串的答案。
一种显然的想法是,仿照求最终答案的方法递归计算,然而这个时间复杂度……不说了。于是我们考虑目前情况和求最终答案的情况有哪些不同之处,这个不同之处就在于,我们目前的情况处于一个大回文串中,我们需要进一步发掘相关的性质来加快速度。我们发现,因为我们已知该串在一个回文串内,所以该串的一个前缀的答案,就等于一个更短的回文串的答案1
上述性质启发我们,按照回文串长度从小到大的顺序进行DP。而考虑到一个回文串时,我们可以在某个串后加上一个字符再翻转得到这个串,这样答案就等于,当前串去掉头尾后得到的回文串的答案加上1。然而我们也可以在一个回文串前插入若干字符再翻转得到这个串,显然这个回文串应该是半串的最长后缀回文串,这样答案就等于这个回文串的答案加上要插入的字符数,再加上1
那么问题的关键就在于如何找到我们需要的回文串的位置。显然应该建一个回文自动机,这样去掉头尾后的回文串就是它在回文自动机上的父亲,而要求长度小于等于半串长的最长后缀回文串,直接在fail树上跳显然是不行的,会TLE,正确的方法是,因为我们之前已经求了它在回文自动机上父亲的对应位置,那么我们只需要从这个对应位置开始匹配即可,这样复杂度就变成了均摊O(n),于是我们就解决了这一题。
以下是本人代码:

#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; 
}
posted @ 2018-06-16 13:55  Maxwei_wzj  阅读(156)  评论(0编辑  收藏  举报