题意
有AGTC四种字符,一开始有一个空串,每次操作,可以在首或尾加任意个字符,或者将已有字符镜面复制(左右两种复制方法),要求最少的操作步数使得得到给出的字符串
题解
首先,最后的串一定是从他的回文子串向两侧添加得到的
所以,只需要考虑构成回文串的最小操作数
设回文树中一节点 u, 其答案为 dp[u],长度为 len[u],其父亲为 v
那么,什么样的串才能得到回文串呢?
- 从它的回文子串得到
- 从它的非回文子串得到,只能通过向两侧补齐得到,最笨的方法
首先说从回文子串得到
若回文子串和本身同对称轴,那么对应的最优解就是PAM结点的父节点 v,那么,在 v 镜面对称之前,先在一侧加上一个字符,再对称,则可以得到 u
否则,若其不是 u 的一个长度小于 len[u]/2 的后缀回文串,只能通过暴力向两侧加,否则,将其补到一半,对称一下即可
可以发现,除了从 v 和 u 的后缀回文串得到,其他的得到方式都是暴力,而且,这两种方式最多会退化成暴力,所以,只考虑回文串之间的转移就行!!!
那么只要在回文树上 dp 就好了
- 若长度为奇数, dp[u]=dp[fail[u]]+len[u]−len[fail[u]]
- 若长度为偶数, dp[u]=min(dp[v]+1,dp[half[u]]+2len[u]−len[half[u]]+1)
其中, half[u] 为结点 u 的长度小于其一半的最长后缀回文串
代码
#include<bits/stdc++.h>
#define N 100010
#define INF 0x3f3f3f3f
#define eps 1e-6
#define pi 3.141592653589793
#define mod 777777777
#define P 1000000007
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<" : "<<x<<endl
#define mem(x,y) memset(x,0,sizeof(int)*(y+3))
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
typedef pair<int,int> pp;
char s[N];
int ans;
int tot;
struct PAM {
int next[N][4] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[N] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[N]; //表示节点i表示的回文串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[N] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数(包括本身)。
int len[N] ;//len[i]表示节点i表示的回文串的长度
int s[N] ;//存放添加的字符
int half[N];
int dp[N];
int last ;//指向上一个字符所在的节点,方便下一次add
int n ;//字符数组指针
int p ;//节点指针
//r为结尾的回文串的长度一定可以分成logn段等差数列
inline int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < 4 ; ++ i ) next[p][i] = 0 ;
cnt[p]= 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}
inline void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
s[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
half[0]=half[1]=1;
}
inline int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( s[n - len[x] - 1] != s[n] ) x = fail[x] ;
return x ;
}
inline void add ( int c ) {
// c -= 'a' ;
s[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;
next[cur][c] = now ;
if (len[now]==1) half[now]=0;else{
int pos=half[cur];
while ( s[n - len[pos] - 1] != s[n]||len[pos]+2>len[now]/2 ) pos = fail[pos] ;
half[now]=next[pos][c];
}
if (len[now]&1)
dp[now]=dp[fail[now]]+len[now]-len[fail[now]];
else
if (len[now]<=2) dp[now]=len[now];
else
dp[now]=min(dp[cur]+1,dp[half[now]]+len[now]/2-len[half[now]]+1);
}
last = next[cur][c] ;
ans=min(ans,dp[last]+tot-len[last]);
}
}A;
int main(int argc, char const *argv[])
{
int T; sc(T);
while(T--){
scanf("%s",s+1);
int n=strlen(s+1); tot=n;
A.init();
ans=1e9;
for(int i=1;i<=n;i++) {
int x;
if (s[i]=='A') x=0;else
if (s[i]=='G') x=1;else
if (s[i]=='C') x=2;else
x=3;
A.add(x);
}
printf("%d\n",ans);
}
return 0;
}