1. 程式人生 > 實用技巧 >【BZOJ4044】[CERC2014] Virus synthesis(迴文自動機上DP)

【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;
}