嗯……备战CCPC?合肥你好
显然只能尽可能地构造一个回文串,其他的就往里面加是了
搞出来PAM,dp[i]为构建长为i的串的最小步数
对于奇数串dp[i]=len[i]
否则dp[i]=min(dp[fa[i]]+1,len[i]/2-len[half[i]]+dp[half[i]]+1)
即究竟是在上一个串末尾添加一个字符翻转
还是由一个串前面加点东西翻过来
half表示i的不超过len[i]/2的最长回文后缀
half的维护在某种意义上暴力就好了
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
char s[maxn];
namespace PAM{
int tot,last,str[maxn],nxt[maxn][4],n;
int len[maxn],suf[maxn],cnt[maxn];
int half[maxn],dp[maxn];
int newnode(int l){
len[tot]=l;
suf[tot]=0;
cnt[tot]=0;
half[tot]=0;
dp[tot]=0;
memset(nxt[tot],0,sizeof nxt[tot]);
return tot++;
}
void init(){
tot=0;n=0;last=0;
newnode(0);// tree0 is node 0
newnode(-1);// tree-1 is node 1
str[0]=-1;
suf[0]=1;
}
int find(int x){
while(str[n-len[x]-1]!=str[n])x=suf[x];
return x;
}
void add(int c){
str[++n]=c;
int u=find(last);
if(!nxt[u]){
int v=newnode(len[u]+2);
suf[v]=nxt[find(suf[u])];
if(len[v]<=2)
half[v]=suf[v];
else{
int x=half[u];
while(str[n-len[x]-1]!=str[n] || (len[x]+2)*2>len[v])x=suf[x];
half[v]=nxt[x];
}
nxt[u]=v;
}last=nxt[u];
cnt[last]++;
}
void count(){
for(int i=tot-1;i>=0;i--)cnt[suf[i]]+=cnt[i];
}
};
using namespace PAM;
int main(){
freopen("bzoj4044.in","r",stdin);
int T;
scanf("%d",&T);
while(T--){
scanf("%s",s+1);
int n=strlen(s+1);
init();
for(int i=1;i<=n;i++){
int x;
if(s[i]=='A')x=0;
if(s[i]=='T')x=1;
if(s[i]=='C')x=2;
if(s[i]=='G')x=3;
add(x);
}
queue<int>q;
q.push(1);
q.push(0);
dp[0]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<4;i++){
if(nxt[u][i]){
int v=nxt[u][i];
// cout<< u <<"->" << v <<endl;
if(len[v]%2==1){
dp[v]=len[v];
}else{
dp[v]=min(dp[u]+1,len[v]/2-len[half[v]]+dp[half[v]]+1);
}
q.push(v);
}
}
}
//for(int i=2;i<tot;i++)
// printf("%d\tsuf=%d\thalf=%d\tdp=%d\n",i,suf[i],half[i],dp[i]);
int ans=n;
for(int i=2;i<tot;i++)
ans=min(ans,n-len[i]+dp[i]);
printf("%d\n",ans);
}
return 0;
}