题目大意
一共有 T 组询问,每组询问一个只包含ACGT的字符串,求通过以下两种操作,最少能得到该串的操作次数。
- 将当前串翻转后接到原串中
- 在当前串首或串尾加入一个字符
Data Constraint
对于100%的数据:
题解
可以注意到操作中有一个翻转,每次操作完以后必然是一个回文串。可以发现目标串必然是由一个回文串(可能为空)再直接首尾添加字符得来。如果我们求出目标串中每一个回文串的最少操作次数,就可以得出答案了。这里可以用回文树来处理。
设
f[i]
表示回文树上的第
i
<script type="math/tex" id="MathJax-Element-488">i</script>个节点的回文串最少需要的操作次数,然后就可以转移了。比较显然的是,奇数长度的回文串并不会对答案造成影响(奇数回文串必能由偶数回文串得来,而且无法翻转得来)。而偶数回文串可以由他的父亲节点增加一对字符得来,这只需要+1次操作(因为可以先添加字符再翻转),或者可以由他的某个长度小于其一半的后缀转移来(语文功底有限所以比较绕 = = 详见程序)
然后直接枚举选择某个回文串,求最小值就是答案。
SRC
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;
#define N 100000 + 10
struct Tree {
int Son[4] ;
int cost , len , fail ;
} T[N] ;
char S[N] ;
int Tab[N] ;
int Test , Last , L , tot , ans ;
int Find( int now , int half ) {
while ( T[now].len > half ) now = T[now].fail ;
return now ;
}
int Getfail( int now ) {
while ( S[L-T[now].len-1] != S[L] ) now = T[now].fail ;
return now ;
}
void ADD( int c ) {
L ++ ;
int now = Getfail( Last ) ;
if ( !T[now].Son[c] ) {
T[++tot].fail = T[Getfail(T[now].fail)].Son[c] ;
T[now].Son[c] = tot ;
T[tot].len = T[now].len + 2 ;
if ( (T[tot].len & 1) == 0 ) {
int j = Find( T[tot].fail , T[tot].len / 2 ) ;
T[tot].cost = min( T[now].cost + 1 , T[j].cost + T[tot].len / 2 - T[j].len + 1 ) ;
} else T[tot].cost = T[tot].len ;
}
Last = T[now].Son[c] ;
}
int main() {
freopen( "gene.in" , "r" , stdin ) ;
freopen( "gene.out" , "w" , stdout ) ;
scanf( "%d" , &Test ) ;
Tab['A'] = 0 , Tab['C'] = 1 , Tab['G'] = 2 , Tab['T'] = 3 ;
while ( Test -- ) {
memset( T , 0 , sizeof(T) ) ;
T[0].fail = T[0].cost = 1 ;
T[1].len = -1 , T[1].cost = 2 ;
Last = S[0] = 0 ;
tot = 1 , L = 0 ;
scanf( "%s" , S + 1 ) ;
ans = strlen( S + 1 ) ;
for (int i = 1 ; i <= ans ; i ++ )
ADD( Tab[S[i]] ) ;
for (int i = 2 ; i <= tot ; i ++ ) ans = min( ans , T[i].cost + L - T[i].len ) ;
printf( "%d\n" , ans ) ;
}
return 0 ;
}
以上.