说在前面
me真的…觉得自己很棒棒
一个小时不到敲完了,提交然后WA,以为自己板子写错了,然而对照着之前的代码发现并没有错
然后me在花式TLE,RE,WA之后,确定是有一个地方搞成了NULL,然而逻辑上来说并不可能…
然后继续对照着网上的代码查错,换了各种写法,还是会RE
最后发现me数组开小了???心态爆炸
题目
题目大意
给出一个仅由AGCT四个字符组成的目标串,您需要通过以下两种操作 通过一个初始为空的串 得到目标串
1. 在已有串的 最前面 或者 最后面 添加一个字符
2. 将已有串复制,并反向接在当前串后面(比如ACT,变换后为ACTTCA)
需要求出最少的操作次数
输入输出格式
输入格式:
第一行一个整数T,表示数据组数
接下来每行一组数据,包含一个字符串(仅由AGTC组成),表示目标串
输出格式:
当然是输出答案啦qwq
每组数据一行一个整数
数据范围:保证单串长不超过100000,保证正解可以过
解法
第二天一早就来填坑
可以发现,比起添加字符操作来说,倍增操作简直是太优秀了(次数少,增加长度多)
然而倍增只能得到一个回文串,因此我们需要找出目标串中的一个回文串S,使用(倍增+添加字符)的方法拼凑出S,剩下的部分则直接使用(添加字符)的方法进行拼凑
下面假设N是目标串长度
那么定义出dp数组:dp[i]表示拼凑出第i个回文串所需的最小代价,那么
ans=min(ans,N−len[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更新)
- 直接令dp[xSx] = len[xSx]即可,因为即使有更优的策略,转移也会被其他转移覆盖
上文一直回避了一个问题,该怎样统计回文串?很明显是回文自动姬
对于偶数回文串通过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 ) ;
}
}