1. 程式人生 > >[CERC2014]Virus synthesis

[CERC2014]Virus synthesis

題目描述

題解

貌似只有迴文樹的解法。

迴文樹

一種自動機,可以識別所有的迴文串。

性質:一個串中本質不同的迴文串最多有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][26
],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; }
View Code

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