【BZOJ4044】[CERC2014] Virus synthesis(回文自动机上DP)
大致题意: 有一个字符串\(S\),初始为空,你可以进行两种操作:在\(S\)的前面或后面添加一个字符;将\(S\)复制并翻转得到\(S'\),然后把\(S'\)接到\(S\)后面。求最少需要几次操作才能得到给定串。
回文自动机
关于回文自动机可以看这篇博客:初学回文自动机。
这里补充一个相关定义:\(trans[x]\)。表示\(x\)所代表的回文串中,长度小于等于\(x\)长度一半的最长回文后缀所对应的节点。
至于如何求出\(trans[x]\),不难发现它和\(fail[x]\)定义很像,因此求法也差不多,只要在\(while\)循环中加上一句判断长度是否大于\(x\)长度一半即可。
具体实现可详见代码。
动态规划
设\(f_x\)表示得到节点\(x\)表示的字符串所需的最小代价。
由于回文自动机上的字符串都是回文串,因此我们\(BFS\)遍历回文自动机,然后每次枚举\(k\)的后继状态\(x\),显然有两种转移:
- 直接从\(k\)在首位各添一个字符得到,由于有回文操作,因此这一步实际上只需要\(1\)的代价。即:
\[f_x=f_k+1
\]
- 给\(y=trans[x]\)前面添上若干字符,然后回文操作得到\(x\)(注意这里只需考虑在前面添上字符,是因为在后面添上字符会在上面的转移中考虑到)。即:
\[f_x=f_{y}+1+(\frac{len[x]}2-len[y])
\]
注意由于奇回文串无法通过回文操作得到,在此题中没有贡献,因此\(BFS\)开始时我们只需将偶根加入队列并令\(f_0=1\)即可。
最终答案就是\(\min\{f_x+(n-len[x])\}\)。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n;char s[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class PalindromeAutomation//回文自动机
{
private:
int Nt,lst,q[N+5];struct node {int DP,L,T,F,S[4];}O[N+5];
I int Fail(RI x,CI id) {W(s[id-O[x].L-1]^s[id]) x=O[x].F;return x;}//跳Fail
public:
I void Init() {memset(O,0,sizeof(node)*Nt),O[O[lst=0].F=Nt=1].L=-1;}//初始化清空
I void Ins(CI id)//插入新元素
{
#define GV(c) (c=='A'?0:(c=='T'?1:(c=='G'?2:3)))
RI x=GV(s[id]),t=Fail(lst,id),o;if(!O[t].S[x])//如果没有该儿子
{
O[o=++Nt].L=O[t].L+2,O[o].F=O[Fail(O[t].F,id)].S[x],O[t].S[x]=o;//计算信息
if(O[o].L<=2) O[o].T=O[o].F;else//求trans
{
RI k=O[t].T;W(s[id-O[k].L-1]^s[id]||(O[k].L+2<<1)>O[o].L) k=O[k].F;//注意判断长度是否大于一半
O[o].T=O[k].S[x];//记录trans
}
}lst=O[t].S[x];//更新lst
}
I int Work()//DP
{
for(RI i=1;i<=Nt;++i) O[i].DP=1e9;//初始化
RI i,k,x,y,ans=n,H=1,T=0;O[q[++T]=0].DP=1;W(H<=T)//BFS
{
for(k=q[H++],i=0;i^4;++i) (x=O[k].S[i])&&//枚举后继
(
y=O[x].T,O[x].DP=min(O[k].DP+1,O[y].DP+1+(O[x].L>>1)-O[y].L),//DP转移
ans=min(ans,O[x].DP+n-O[x].L),q[++T]=x//统计答案,加入BFS队列
);
}return ans;
}
}P;
int main()
{
RI Tt,i;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%s",s+1),n=strlen(s+1),P.Init(),i=1;i<=n;++i) P.Ins(i);//建回文自动机
printf("%d\n",P.Work());//输出答案
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒