Bzoj4044:[Cerc2014] Virus synthesis:回文自动机+DP

22 篇文章 0 订阅
3 篇文章 0 订阅

题目链接:4044:[Cerc2014] Virus synthesis

真是一道回文自动机的好题

先建立一发回文自动机,然后设dp[i]为形成节点i所代表的回文串所需最少步骤数,l[i]是i节点代表的回文串的长度

dp[i]的初值要设为l[i],表示最多l[i]步就可以形成i这个回文串

对于串i,他可以在首或者尾加一个字符形成的回文串j:dp[j]=dp[i]+1;

然后我们可以根据j的fail指针找到一个回文串x,这个回文串的长度小于等于l[j]/2,则dp[j]=min(dp[j],dp[x]+1+l[j]/2-l[x]);

我们可以用一个队列存储可以转移的状态,这样就保证了转移是有序的

但是还不够,对于像全是A这样的数据会T

因为我们对于每一个串都会根据fail指针跳很多次

我们考虑预先处理出每个j对应的x,设为t[];

我们可以在构造完fail指针后继续构造t[];

但是不行还会T,即使是这样我们调用比较函数的次数还会高达3亿次!

考虑我们在构造t[j]的时候t[last]是已经构造出来的了,由于j是在last的基础上又跳了一次fail指针,所以t[j]的长度一定不会大于t[last]的长度且我们以前的方法再跳fail指针的时候一定会调到过last,所以我们可以直接以t[last]为基础开始跳

不要以为这个优化不明显……这样只用调用比较函数不到10万次QAQ

然后就过了QAQ

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=200010;
const int maxs=5;
int n,ans,dp[maxn],q[maxn];
char s[maxn];

inline int getw(char c){
    if (c=='A') return 0;
    else if (c=='T') return 1;
    else if (c=='C') return 2;
    else return 3;
}

struct pam{
    int next[maxn][maxs],ro,re,t[maxn],N;
    int last,sz,l[maxn],fail[maxn],str[maxn];
    void init(){
        sz=0; str[0]=-1; N=0;
		++sz; ro=sz; l[ro]=-1; memset(next[sz],0,sizeof(next[sz]));
		++sz; re=sz; l[re]=0; memset(next[sz],0,sizeof(next[sz]));
        fail[re]=ro; last=ro;
    }
    bool gofail(int x,int c,int id){
        return str[N-l[x]-1]==c;
    }
    void add(int c,int id){
        str[++N]=c;
        while (!gofail(last,c,id)) last=fail[last];
        if (next[last][c]) last=next[last][c];
        else{
            int x=last; ++sz; next[x][c]=sz; l[sz]=l[x]+2;
            memset(next[sz],0,sizeof(next[sz])); int z=last;
            if (x==ro) fail[sz]=re;
            else{
                x=fail[x];
                while (!gofail(x,c,id)) x=fail[x];
                fail[sz]=next[x][c];
            }last=sz;
            if (l[sz]<=2) t[sz]=fail[sz];
            else {
				z=t[z];
				while (!gofail(z,c,id)||(l[z]+2)*2>l[sz]) z=fail[z];
				t[sz]=next[z][c];
            }
        }
    }
}pt;

int main(){
    int T; scanf("%d",&T);
    for (int balabala=1;balabala<=T;++balabala){
        scanf("%s",s+1);
        int n=strlen(s+1);
        pt.init(); ans=n;
        for (int i=1;i<=n;++i) pt.add(getw(s[i]),i);
        for (int i=3;i<=pt.sz;++i) dp[i]=pt.l[i];
        int h=1,tail=0,now; q[++tail]=2; dp[2]=1;
        while (h<=tail){
            now=q[h++];
            for (int i=0;i<4;++i){
                int x=pt.next[now][i];
                if (!x) continue;
                dp[x]=dp[now]+1;
                int y=pt.t[x];
                dp[x]=min(dp[x],dp[y]+1+pt.l[x]/2-pt.l[y]);
                ans=min(ans,dp[x]+n-pt.l[x]);
                q[++tail]=x;
            }
        }printf("%d\n",ans);
    }
}





  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值