jzoj 4387. 【GDOI2016模拟3.15】基因合成 回文树+dp

Description
这里写图片描述
Input
这里写图片描述
Output
这里写图片描述

Sample Input
4
AAAA
AGCTTGCA
AAGGGGAAGGGGAA
AAACAGTCCTGACAAAAAAAAAAAAC
Sample Output
3
8
6
18

Data Constraint
这里写图片描述

分析:
一个串可以看作是某一个偶回文串在左右添加若干字符得到,这就要使用回文树来构造回文串。

回文树教学

既然大家学会了,那就可以直接dp做了。我们设 f [ s ] 为串 s 通过多少次变化,且最后一次操作一定为 2 操作的步数。
因为如果最后一次不是 2 操作,那么这个回文串 s 1 一定从前面一个回文串 s 2 左右加若干字符得到,而又要把这个串左右加至目标串,相当于直接把前面的回文串 s 2 直接加到目标串。

所以,当 s 为奇数串, f [ s ] = l e n ( s )
s 为偶数串时,一种是从他的父亲 t 1 转移,即在 t 的两端加一个字符;第二种是找到一个长度不超过 l e n ( s ) / 2 的后缀回文串 t 2 ,这个与 p r e 指针有关,先把该串添加到 l e n ( s ) / 2 ,再进行翻倍。可以发现,第一种转移相当于往两边插入一个字符,第二种是把后缀填满,相当于往中间插入若干字符。

转移方程式为,

f [ s ] = min ( f [ t 1 ] + 1 , f [ t 2 ] + ( l e n ( s ) l e n ( t 2 ) + 1 ) )

第二种转移是很显然的。因为我们每个串最后一次都是 2 操作,所以相当于先回到 t 1 的前一步,即 t 1 串的一半,然后加入一个字符,再翻倍,所以相当于只用多了一步。可以知道的是,任何一种状态的转移,都能用成这两种转移表示。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>

const int maxn=1e5+7;

using namespace std;

int test,n,p,cnt;
char s[maxn];
int f[maxn];

queue <int> q;

struct node{
    int fail,last,len;
    int vis[4];
}t[maxn];

int po(char ch)
{
    if (ch=='A') return 0;
    if (ch=='G') return 1;
    if (ch=='C') return 2;
    if (ch=='T') return 3;
}

void add(int x)
{
    int k=po(s[x]);
    while (s[x]!=s[x-t[p].len-1]) p=t[p].fail;
    if (!t[p].vis[k])
    {
        int i;
        cnt++;
        for (i=t[p].fail;i!=1;i=t[i].fail)
        {
            if (s[x]==s[x-t[i].len-1]) break;
        }
        t[cnt].fail=t[i].vis[k];
        t[p].vis[k]=cnt;
        t[cnt].len=t[p].len+2;
        for (i=cnt;i!=1;i=t[i].fail)
        {
            if (t[i].len*2<=t[cnt].len) break; 
        }
        t[cnt].last=i;
        if (t[cnt].len%2) f[cnt]=t[cnt].len;
        else f[cnt]=min(f[p]+1,f[t[cnt].last]+(t[cnt].len/2-t[t[cnt].last].len)+1);
    }
    p=t[p].vis[k];
}

void build()
{   
    memset(t,0,sizeof(t));
    memset(f,0,sizeof(f));
    f[0]=1;
    n=strlen(s);
    t[0].fail=1;
    t[1].len=-1;
    cnt=1;      
    for (int i=0;i<n;i++) 
    add(i);
}

int main()
{
    scanf("%d",&test);
    while (test--) 
    {
        scanf("%s",s);  
        build();                
        int ans=n;      
        for (int i=2;i<=cnt;i++) ans=min(ans,f[i]+n-t[i].len);
        printf("%d\n",ans);
    }
} 

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/81328509