一、題目
二、解法
可以發現答案的構成一定是這個樣子的:一個迴文串,其它暴力填,因爲構造迴文一定比暴力填要優。
設爲達到自動機上狀態所需要的步數,最終的答案是求最小,爲了保證更新順序,我們用隊列來更新,更新方法如下:
- 從父節點跳轉移而來,也就是 ,因爲我們可以再回文翻倍之前加入我們需要的字符,所以只需要多花費的步數。
- 我們先處理出指針,即不大於原長度的最長迴文後綴,當前迴文串可以由迴文翻倍過來,我們再把不夠的部分暴力填上,所以這樣更新:
然後的構造方法及一些細節都不說了,看代碼把qwq。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 500005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int T,n,ans;char s[M];
struct Pam
{
int n,last,cnt,fail[M],len[M],tr[M],ch[M][4],dp[M];
void clear()
{
fail[0]=1;len[1]=-1;
cnt=1;last=n=0;
}
int get_fail(int x)
{
while(s[n-len[x]-1]^s[n])
x=fail[x];
return x;
}
void ins()
{
n++;
int p=get_fail(last),c=s[n]-'A';
if(!ch[p][c])
{
len[++cnt]=len[p]+2;
int tmp=get_fail(fail[p]);
fail[cnt]=ch[tmp][c];
ch[p][c]=cnt;
if(len[cnt]<=2) tr[cnt]=fail[cnt];
else
{
tmp=tr[p];
while(s[n-len[tmp]-1]^s[n] || ((len[tmp]+2)<<1)>len[cnt])
tmp=fail[tmp];
tr[cnt]=ch[tmp][c];
}
}
last=ch[p][c];
}
void sol()
{
int ans=n;
queue<int> q;
q.push(0);dp[0]=1;
for(int i=2;i<=cnt;i++) dp[i]=len[i];
//需要賦值是因爲1爲根的狀態不會被訪問,但需要用到
while(!q.empty())
{
int t=q.front();
q.pop();//用隊列更新qwq
for(int i=0;i<4;i++)
{
int to=ch[t][i];
if(!to) continue;
dp[to]=dp[t]+1;
dp[to]=min(dp[to],dp[tr[to]]+1+len[to]/2-len[tr[to]]);
ans=min(ans,dp[to]+n-len[to]);
q.push(to);
}
}
printf("%d\n",ans);
}
}A;
int main()
{
T=read();
while(T--)
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=0;i<=n+1;i++)
for(int j=0;j<4;j++)
A.ch[i][j]=0;//不能memset,暴力mem見祖宗
A.clear();
for(int i=1;i<=n;i++)
{
if(s[i]=='C') s[i]='B';//離散一下qwq
if(s[i]=='G') s[i]='C';
if(s[i]=='T') s[i]='D';
A.ins();
}
A.sol();
}
}