Description
Input
Output
Sample Input
4
AAAA
AGCTTGCA
AAGGGGAAGGGGAA
AAACAGTCCTGACAAAAAAAAAAAAC
Sample Output
3
8
6
18
Data Constraint
分析:
一个串可以看作是某一个偶回文串在左右添加若干字符得到,这就要使用回文树来构造回文串。
既然大家学会了,那就可以直接dp做了。我们设
f[s]
f
[
s
]
为串
s
s
通过多少次变化,且最后一次操作一定为操作的步数。
因为如果最后一次不是
2
2
操作,那么这个回文串一定从前面一个回文串
s2
s
2
左右加若干字符得到,而又要把这个串左右加至目标串,相当于直接把前面的回文串
s2
s
2
直接加到目标串。
所以,当
s
s
为奇数串,。
当
s
s
为偶数串时,一种是从他的父亲转移,即在
t
t
的两端加一个字符;第二种是找到一个长度不超过的后缀回文串
t2
t
2
,这个与
pre
p
r
e
指针有关,先把该串添加到
len(s)/2
l
e
n
(
s
)
/
2
,再进行翻倍。可以发现,第一种转移相当于往两边插入一个字符,第二种是把后缀填满,相当于往中间插入若干字符。
转移方程式为,
第二种转移是很显然的。因为我们每个串最后一次都是 2 2 操作,所以相当于先回到的前一步,即 t1 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);
}
}