[BZOJ4044]-Virus synthesis-回文自动姬+DP

说在前面

me真的…觉得自己很棒棒
一个小时不到敲完了,提交然后WA,以为自己板子写错了,然而对照着之前的代码发现并没有错
然后me在花式TLE,RE,WA之后,确定是有一个地方搞成了NULL,然而逻辑上来说并不可能…
然后继续对照着网上的代码查错,换了各种写法,还是会RE

最后发现me数组开小了???心态爆炸


题目

BZOJ4044传送门

题目大意

给出一个仅由AGCT四个字符组成的目标串,您需要通过以下两种操作 通过一个初始为空的串 得到目标串
1. 在已有串的 最前面 或者 最后面 添加一个字符
2. 将已有串复制,并反向接在当前串后面(比如ACT,变换后为ACTTCA)
需要求出最少的操作次数

输入输出格式

输入格式:
第一行一个整数T,表示数据组数
接下来每行一组数据,包含一个字符串(仅由AGTC组成),表示目标串

输出格式:
当然是输出答案啦qwq
每组数据一行一个整数

数据范围:保证单串长不超过100000,保证正解可以过


解法

第二天一早就来填坑
可以发现,比起添加字符操作来说,倍增操作简直是太优秀了(次数少,增加长度多)
然而倍增只能得到一个回文串,因此我们需要找出目标串中的一个回文串S,使用(倍增+添加字符)的方法拼凑出S,剩下的部分则直接使用(添加字符)的方法进行拼凑

下面假设N是目标串长度
那么定义出dp数组:dp[i]表示拼凑出第i个回文串所需的最小代价,那么 ans=min(ans,Nlen[i]+dp[i])
关于dp的转移,需要根据回文串的情况去讨论

  • 对于偶数回文串有:

    • xSSx的情况,看作是S在翻倍前先添加了一个x,即dp[xSSx] = dp[SS] + len[x]
    • SxxS的情况,同样的dp[SxxS] = (dp[S] + len[x])+1
    • 不考虑从原有回文串添加得来(S -> Sxx),因为这样步数为2,所以一定不如第一种转移优
  • 对于奇数回文串(假设当前回文串为xSx,这里x,y不一定只代表一个字符):

    • 直接令dp[xSx] = len[xSx]即可,因为即使有更优的策略,转移也会被其他转移覆盖
      • 如果xSx中,Sx是一个回文串,那么显然Sx长度为偶数,该种转移会被dp[Sx]覆盖
      • 如果在xSx中,存在一个偶回文串L且L的长度不超过xSx的一半。在这样的情况下,dp[xSx] = dp[L] + len[xSx] - len[L],再使用dp[xSx]去更新其他的dp值,实际相就相当于使用dp[L]去更新其他的dp值(或答案),因此这种转移也是不必要的(如果被更新方式是暴力填补空缺,显然。只考虑倍增更新:Q = xSx + xSx,即Q = xLyLx + xLyLx = x+(LyLxxLyL)+x,显然这种情况会被 偶数回文串LyLxxLyL通过在两边暴力添加x更新)

上文一直回避了一个问题,该怎样统计回文串?很明显是回文自动姬
对于偶数回文串通过S的转移,S的长度不能超过xSSx(SxxS)的一半,这个只需要再维护一个half_fail指针就可以了,具体看代码


下面是me再也不想看见的代码

/**************************************************************
    Problem: 4044
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:5880 ms
    Memory:13028 kb
****************************************************************/
 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
 
int T , N , Node_cnt , ans , f[100005] ;
char ss[100005] ;
struct Node{
    int len , id ;
    Node *ch[10] , *fail , *half_fail ;
//  charater_map :: A(1) C(2) G(3) T(4) 
}w[200005] , *tw = w , *root0 , *root1 , *last ;
 
void newNode( Node *&nd , int len ){
    nd = ++tw ; nd->id = ++Node_cnt ;
    nd->len = len ;
    nd->fail = nd->half_fail = NULL ;
    memset( nd->ch , 0 , sizeof( nd->ch ) ) ;
}
 
Node *getFail( Node *tmp , int pos ){
    while( ss[ pos-tmp->len-1 ] != ss[pos] ) tmp = tmp->fail ;
    return tmp ;
}
//  charater_map :: A(1) C(2) G(3) T(4) 
void Insert( const short &id , int pos ){
    Node *p = getFail( last , pos ) ;
    if( !p->ch[id] ){
        Node *tmp = getFail( p->fail , pos )->ch[id] , *&nd = p->ch[id] ;
        newNode( nd , p->len + 2 ) ;
        nd->fail = ( tmp ? tmp : root0 ) ;
        if( nd->len <= 2 ) nd->half_fail = nd->fail ;
        else{
            tmp = p->half_fail ;
            while( ss[ pos-tmp->len-1 ] != ss[pos] || ( tmp->len+2 ) * 2 > nd->len )// 最终fail为child, len比tmp大2
                tmp = tmp->fail ;
            nd->half_fail = tmp->ch[id] ;
        }
    }
    last = p->ch[id] ;
}
 
int tp[100005] , sa[100005] ;
void Rsort(){
    for( int i = 1 ; i <= N ; i ++ ) tp[i] = 0 ;
    tp[0] = 2 ;// root0 and root1
    for( int i = 3 ; i <= Node_cnt ; i ++ ) tp[ w[i].len ] ++ ;//except root0 and root1 
    for( int i = 1 ; i <= N ; i ++ ) tp[i] += tp[i-1] ;
    for( int i = Node_cnt ; i >= 3 ; i -- ) sa[ tp[w[i].len]-- ] = i ;
}
 
void solve(){
    for( int i = 1 ; i <= N ; i ++ )
        Insert( ss[i] , i ) ;
    Rsort() ;
    for( int i = 1 ; i <= Node_cnt ; i ++ )
        f[i] = w[i].len ; // memset巨慢
    for( int i = 3 ; i <= Node_cnt ; i ++ ){
        Node *nd = ( w + sa[i] ) ; int now = sa[i] ;
        if( !(nd->len&1) ){
            f[now] = min( f[now] , f[nd->half_fail->id] + nd->len/2 - nd->half_fail->len + 1 ) ;// SxxS = ( S+(x) )+1 = ( f(S)+len(x) )+1
            for( short k = 1 ; k <= 4 ; k ++ )
            if( nd->ch[k] ) f[nd->ch[k]->id] = min( f[nd->ch[k]->id] , f[now] + 1 ) ; // xSSx = (Sx)+1 = f(S)+1 + x = f(SS) + x 
        }
        ans = min( ans , N - nd->len + f[now] ) ; // ...S... = len( ... ) + f(S) ;
    }
}
//  charater_map :: A(1) C(2) G(3) T(4) 
void init(){
    tw = w ; Node_cnt = 0 ;
    ans = 0x3f3f3f3f ;
    newNode( root1 , -1 ) ; root1->fail = root1 ;
    newNode( root0 , 0  ) ; root0->fail = root1 ;
    last = root0 ;
}
 
int main(){
    scanf( "%d" , &T ) ;
    while( T -- ){
        init() ;
        scanf( "%s" , ss + 1 ) ; N = strlen( ss + 1 ) ;
        for( int i = 1 ; i <= N ; i ++ )
             if( ss[i] == 'A' ) ss[i] = 1 ;
        else if( ss[i] == 'C' ) ss[i] = 2 ;
        else if( ss[i] == 'G' ) ss[i] = 3 ;
        else if( ss[i] == 'T' ) ss[i] = 4 ;
        solve() ;
        printf( "%d\n" , ans ) ;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值