题解 P4762 Virus synthesis

· · 题解

Virus synthesis

首先,最后的串一定是一个最后一次翻转得到的回文串在首尾加上一些字符得到的,所以我们最多只能利用原串中的一个回文串。

对原串建出回文自动机。设 s_u 为点 u 所代表的串,dp_u 表示构造 s_u 的最小操作次数,那么答案就是 \min_u |S|-len_u+dp_u

引理 1:如果要得到一个长度为偶数的回文串 A,那么最后一次操作肯定是翻转。

证明|A|=2 时,显然先填一个字符然后翻转不劣。

如果 |X| \in [2,l] 时构造 X 的最后一步都是翻转,|A|=|X|+1

如果最后一次操作不是翻转,找到最后一次翻转出来的回文串 B 所在的位置(如没有翻转操作,那么 |B|=0)。设 |A|=2a,|B|=2b,构造 B 的一半需要操作 d 次。

引理 2:长度为奇数的回文串对于答案没有影响。

证明:长度为奇数的回文串 A 不能由翻转得到,那么找到构造它的过程中最后一次翻转操作翻转出的串 B(如果没有翻转操作那么 |B|=0)。A 的转移全部可以视为由 B 转移得到。

下面只考虑偶回文串。

考虑计算 dp_u

引理 3:这里如果一个回文前缀不是 s_u 的长度不超过 \frac{s_u}{2} 的最长回文前缀 s_{h_u},那么无需从它转移。

证明:其他长度不超过 \frac{|s_u|}{2} 的回文前缀 X 都是 s_{h_u} 的前缀,X 在后面添加字符得到 s_u 的一半然后翻转得到 s_u。那么添加字符到 h_u 时,由引理 1,我们没有通过翻转得到 s_{h_u},此时肯定不优,不如通过 s_{h_u} 转移。 那么有转移 dp_u \overset{\min}{\longleftarrow} dp_{h_u}+\left(\frac{|s_u|}{2}-|s_{h_u}|\right)+1

最后,需要计算 $h$。如果一个串 $s_u$ 通过在后面添加字符得到 $s_v$(即 $u$ 是 $v$ 在 PAM 的 fail 树上的祖先),那么 $s_u$ 的回文前缀也是 $s_v$ 的回文前缀。又 $|s_u|<|s_v|$,那么有 $s_{h_u}$ 是 $s_{h_v}$ 的一个前缀。所以 $h_u$ 在回文自动机上的深度关于 $u$ 的深度有单调性。那么设 $u$ 在 trie 树上的父亲为 $f$、$f$ 到 $u$ 经过字符边 $c$,我们先设 $w=h_f$,不断跳 $fail_w$ 直到 $len_w+2 \le len_u$、$w$ 有 $c$ 儿子且 $s_u$ 最后放得下 $cs_wc$(也就是此时的 $s_w$ 前一个字符是 $c$)或者到了奇根,然后令 $h_u$ 为 $w$ 的 $c$ 儿子即可。 总时间复杂度为 $O(n)$。 ```cpp #include<bits/stdc++.h> using namespace std; int trans(char c) { static const string charset="ATCG"; for(int i=0;i<=3;i++) { if(charset[i]==c) return i; } return -1; } struct PalimdromeAutomaton { struct Node { int ch[4]; int len; int fth; int fil,hlf; }; string s; vector<Node> t; int lst; int newNode() { t.push_back((Node){{0},0,0,0,0}); return t.size()-1; } PalimdromeAutomaton() { s=""; t.clear(); newNode(); newNode(),newNode(); t[1].fil=2,t[2].fil=1; t[1].len=-1,t[2].len=0; t[1].hlf=1,t[2].hlf=2; lst=2; } int findFail(int u,int pos,char c) { while(u!=1&&s[pos-t[u].len-1]!=c) u=t[u].fil; return u; } void insert(char c) { int x=trans(c); s+=c; int f=findFail(lst,s.size()-1,c); if(!t[f].ch[x]) { int u=newNode(); t[u].len=t[f].len+2; int tmp=findFail(t[f].fil,s.size()-1,c); t[u].fil=(t[tmp].ch[x]?t[tmp].ch[x]:2); if(t[u].len<=1) t[u].hlf=2; else { int v=t[f].hlf; while(v!=1&&(t[v].len+2>t[u].len/2||!t[v].ch[x]||s[s.size()-t[v].len-2]!=c)) v=t[v].fil; t[u].hlf=t[v].ch[x]; } t[f].ch[x]=u; t[u].fth=f; } lst=t[f].ch[x]; } }; PalimdromeAutomaton pam; int dp[110000]; void calcDP() { memset(dp,127,sizeof(dp)); dp[2]=0; for(int i=0;i<=3;i++) { dp[pam.t[2].ch[i]]=2; } for(int i=1;i<pam.t.size();i++) { if(pam.t[i].len%2==1||pam.t[i].len<=2) continue; if(pam.t[pam.t[i].fth].len%2==0) dp[i]=min(dp[i],dp[pam.t[i].fth]+1); if(pam.t[pam.t[i].hlf].len%2==0) dp[i]=min(dp[i],dp[pam.t[i].hlf]+pam.t[i].len/2-pam.t[pam.t[i].hlf].len+1); } } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin>>T; for(int _=1;_<=T;_++) { string s; cin>>s; pam=PalimdromeAutomaton(); for(int i=0;i<s.size();i++) { pam.insert(s[i]); } calcDP(); int ans=1e9; for(int i=1;i<pam.t.size();i++) { if(pam.t[i].len%2==0) { ans=min(ans,(int)(s.size()-pam.t[i].len+dp[i])); } } cout<<ans<<'\n'; } } ```