[CERC2014]Virus synthesis
[CERC2014]Virus synthesis
题意:
初始有一个空串,有以下操作:
- 串开头或末尾添加一个字符
- 向前复制,形成回文串
询问要操作成给定的串需要多少次操作。
分析:
首先,我们还是先求出来对应给定的串的后缀自动机。
可以大致推算出来,这种题目一般都是 \(DP\) ,我们设 \(dp[i]\) 表示转移到 \(i\) 节点代表的回文串的最少需要次数。
因为我们尽量想让操作变少,因此肯定操作二越多越好。
因此最后的答案肯定是由一个回文串+暴力添加 的来,答案为 \(min(dp[i]+n-len[i])\)
对于一个串 \(i\) ,如果前后加一个字母形成 \(j\), 则 \(dp[j]=dp[i]+1\)
我们通过 \(trans\) 找到最长的 \(x\) 后缀回文串 \(x\) 满足 \(len[x] \leq len[i]/2\)
那么则有 \(dp[i]=min(dp[i],dp[x]+1+len[i]/2-len[x])\) ,即暴力后复制。
绿色部分即为我们要添加的部分,因此就为上面公式
然后用队列记录状态,保证转移到有序的。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T;
int fail[N],trans[N],n,val[N];
int son[N][5],last,len[N],cnt,ans;
char s[N];
int dp[N],que[N];
int new_node(int x){
len[++cnt]=x;
memset(son[cnt],0,sizeof(son[cnt])*5);
return cnt;
}
int getfail(int x,int now){
while(s[now-len[x]-1]!=s[now]) x=fail[x]; return x;
}
void init(){
memset(son[0],0,sizeof(int)*5); memset(son[1],0,sizeof(int)*5);//直接清理,会炸第一个点
val['A']=0, val['T']=1,val['C']=2, val['G']=3;
s[0]=-1,last=0,cnt=1;fail[0]=1,len[1]=-1;
}
void build_PAM(){
for(int i=1;i<=n;i++){
int now=getfail(last,i),x=val[s[i]];
// cout<<now<<endl;
if(!son[now][x]){
int newnode=new_node(len[now]+2);
fail[newnode]=son[getfail(fail[now],i)][x];
son[now][x]=newnode;
if(len[newnode]<=2) trans[newnode]=fail[newnode];
else{
int tmp=trans[now];
while(s[i-len[tmp]-1]!=s[i]||((len[tmp]+2)<<1)>len[newnode]) tmp=fail[tmp];
trans[newnode]=son[tmp][x];
}
}
last=son[now][x];
}
}
int main(){
cin>>T;
while(T--){
scanf("%s",s+1); init();
ans=n=strlen(s+1);
build_PAM();
for(int i=2;i<=cnt;i++) dp[i]=len[i];
int L=1,R=0; que[++R]=0, dp[0]=1;
while(L<=R){
int now=que[L++];
for(int i=0;i<4;i++){
int x=son[now][i],y=trans[x];
if(!x) continue;
dp[x]=dp[now]+1;
dp[x]=min(dp[x],dp[y]+1+len[x]/2-len[y]);
ans=min(ans,dp[x]+n-len[x]);
que[++R]=x;
}
}
cout<<ans<<endl;
}
system("pause");
return 0;
}
不关注的有难了😠😠😠https://b23.tv/hoXKV9