[CERC2014]Virus synthesis
阿新 • • 發佈:2018-12-25
題目描述
題解
貌似只有迴文樹的解法。
迴文樹
一種自動機,可以識別所有的迴文串。
性質:一個串中本質不同的迴文串最多有n個。
和其他自動機一樣,它記錄了一個ch陣列,一個fail陣列,fail陣列在這裡的指向是這個串的最長迴文字尾。
在迴文樹中,每個節點都存了一個迴文串,為了區分奇數串和偶數串,1->奇數0->偶數。
為了防止邊界爆炸,len[1]=-1,fail[0]=1。
構造方法:仍是增量構造,我們可以維護一個變變數last,和字尾自動機一樣,表示上一次的節點。
然後我們可以判斷一下s[i-len[x]-1]==s[i]如果不滿足,就跳last的fail,知道找到最長的last的字尾滿足接上當前節點後是一個迴文串。
然後我們新開節點cnt,len[cnt]=len[last]+2。
+2是因為我在last前後各添加了一個字元,所以要+2,。
然後就要連fail了,找到fail[last],和上面一樣跳,然後直接連。
例題
APIO2014迴文串,直接建出迴文樹,統計一下。
#include<iostream> #include<cstdio> #include<cstring> #define N 300002 using namespace std; typedef long long ll; ll ans; char s[N]; int n,tong[N],fail[N],len[N],ch[N][26View Code],cnt,last; int main(){ scanf("%s",s+1);n=strlen(s+1); len[1]=-1;fail[0]=1;s[0]='&';cnt=last=1; for(int i=1;i<=n;++i){ int x=s[i]-'a'; while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][x]){ len[++cnt]=len[last]+2; int j=fail[last];while(s[i-len[j]-1]!=s[i])j=fail[j]; fail[cnt]=ch[j][x]; ch[last][x]=cnt; } last=ch[last][x];++tong[last]; } for(int i=cnt;i>=2;--i){ tong[fail[i]]+=tong[i]; ans=max(ans,1ll*tong[i]*len[i]); } cout<<ans; return 0; }
SHOI2011雙倍迴文:對每一個節點求一個father,表示小於等於這個串一半的最長迴文字尾,求法和求fail類似,然後統計一下(雖然我用的暴力加剪枝)。
#include<iostream> #include<cstdio> #define N 500002 using namespace std; int len[N],last,cnt,fail[N],ch[N][26],n,ans; char s[N]; int main(){ scanf("%d%s",&n,s+1); last=cnt=1;len[1]=-1;fail[0]=1; for(int i=1;i<=n;++i){ while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][s[i]-'a']){ len[++cnt]=len[last]+2; int x=fail[last]; while(s[i-len[x]-1]!=s[i])x=fail[x]; fail[cnt]=ch[x][s[i]-'a'];ch[last][s[i]-'a']=cnt; } last=ch[last][s[i]-'a']; } for(int i=cnt;i>=1;--i){ ///!!! if(len[i]%4)continue; if(len[i]<=ans)continue; int x=i; while(len[x]*2>len[i])x=fail[x]; if(len[x]*2==len[i])ans=max(ans,len[i]); } cout<<ans; return 0; }View Code
然後看這道題。
先搞出來迴文樹。
考慮令dp[i]表示搞出來i這個迴文串的最小代價、
然後我們在0的子樹上轉移,因為奇迴文串不滿足翻轉性質。
dp[v]=dp[u]+1因為是0的子樹,它是偶迴文串,已經被翻過了,所以我們假裝在翻之前就把這個字元填上去了。
dp[v]=dp[fa]+len[v]/2+1,我們在它的father上搞點事情,因為它的長度小於一半,所以我們把它補到一半後翻一下就好了。
那為什麼不考慮前面的往後面翻的情況呢?
因為這兩種情況代價相等。
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #define N 100002 using namespace std; queue<int>q; int ch[N][4],len[N],fail[N],n,t,last,trans[200],cnt,fa[N],dp[N],ans; char s[N]; int main(){ trans['A']=0;trans['T']=1;trans['C']=2;trans['G']=3; scanf("%d",&t); while(t--){ scanf("%s",s+1);n=strlen(s+1); last=cnt=1;fail[0]=1;len[1]=-1;s[0]='#'; for(int i=1;i<=n;++i){ int y=trans[s[i]]; while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][y]){ int x=fail[last]; len[++cnt]=len[last]+2; while(s[i-len[x]-1]!=s[i])x=fail[x]; fail[cnt]=ch[x][y];ch[last][y]=cnt; if(len[cnt]>2){ int tmp=fa[last]; while(s[i-len[tmp]-1]!=s[i]||(len[tmp]+2)*2>len[cnt])tmp=fail[tmp]; fa[cnt]=ch[tmp][y]; }else fa[cnt]=fail[cnt]; } last=ch[last][y]; } ans=n; for(int i=2;i<=cnt;++i)dp[i]=len[i]; dp[0]=1;dp[1]=0; q.push(0); while(!q.empty()){ int u=q.front();q.pop(); for(int i=0;i<4;++i)if(ch[u][i]){ int v=ch[u][i]; dp[v]=min(dp[v],dp[u]+1); int o=fa[v]; dp[v]=min(dp[v],dp[o]+len[v]/2-len[o]+1); ans=min(ans,dp[v]+n-len[v]); q.push(v); } } // for(int i=1;i<=cnt;++i)cout<<dp[i]<<" ";cout<<endl; printf("%d\n",ans); for(int i=0;i<=cnt;++i){ fail[i]=0,fa[i]=0,len[i]=0; for(int j=0;j<4;++j)ch[i][j]=0; } } return 0; }