题目
题目链接:https://www.luogu.com.cn/problem/P4762
初始有一个空串,利用下面的操作构造给定串 (S)。
- 串开头或末尾加一个字符。
- 串开头或末尾加一个该串的逆串。
求最小化操作数,(|S| leq 10^5)。
思路
考虑在 PAM 上 dp。一个节点 (x) 可以被转移的方案只有以下三种:
- 全部采用操作 (1),操作次数为 (mathrm{len}_x)。
- 从其父亲节点转移而来,可以在父节点准备进行操作二之前再加入一个字符,然后再操作二。操作次数为 (f_{fa}+1)。
- 从其某一个长度不超过一般的后缀回文串再加若干个字符,然后再进行操作二。不难发现这个后缀回文串一定是其最长的且长度不超过其长度一般的后缀回文串。操作次数为 (f_{y}+mathrm{frac{mathrm{len}_x}{2}-mathrm{len}_y}+1)。
然后取最优答案即可。
时间复杂度 (O(n))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010,Inf=1e9;
int Q,n,ans,f[N];
char s[N];
int ID(char c)
{
if (c=='A') return 0;
if (c=='C') return 1;
if (c=='G') return 2;
if (c=='T') return 3;
}
struct PAM
{
int last,tot,fail[N][2],ch[N][4],len[N];
void build()
{
for (int i=0;i<=tot;i++)
fail[i][0]=fail[i][1]=len[i]=ch[i][0]=ch[i][1]=ch[i][2]=ch[i][3]=0;
last=0; tot=1; fail[0][0]=1; len[1]=-1;
f[0]=1; f[1]=0;
}
int getfail(int m,int x)
{
while (s[m]!=s[m-len[x]-1]) x=fail[x][0];
return x;
}
void ins(int m,int c)
{
int p=getfail(m,last);
if (!ch[p][c])
{
int np=++tot;
len[np]=len[p]+2; f[np]=len[np];
fail[np][0]=fail[np][1]=ch[getfail(m,fail[p][0])][c];
if (len[np]>2)
{
f[np]=len[np];
int q=fail[p][1];
while (s[m]!=s[m-len[q]-1] || len[ch[q][c]]*2>len[np]) q=fail[q][0];
if (!(len[np]&1)) f[np]=min(f[p]+1,f[ch[q][c]]+len[np]/2-len[ch[q][c]]+1);
fail[np][1]=ch[q][c];
}
ch[p][c]=np;
ans=min(ans,n-len[np]+f[np]);
}
last=ch[p][c];
}
}pam;
void prework()
{
for (int i=0;i<=n;i++) f[i]=Inf;
pam.build(); ans=Inf;
}
int main()
{
scanf("%d",&Q);
while (Q--)
{
scanf("%s",s+1);
n=strlen(s+1);
prework();
for (int i=1;i<=n;i++)
pam.ins(i,ID(s[i]));
printf("%d
",ans);
}
return 0;
}