【分塊+迴文自動機】LibreOJ6070(2017 山東一輪集訓 Day4)[基因]題解

題目概述

給出一個由小寫字母構成的字符串,有 m 個詢問 [l,r] ,表示求 s[l..r] 中本質不同迴文字串的個數,強制在線。

解題報告

因爲強制在線詢問區間,所以我們想到分塊。以每個塊的左端點開始構造後綴的迴文自動機,就可以得到 ans[i][j] 表示從第 i 個塊左端點開始到 j 中不同迴文子串的個數。

然後對於每個詢問,我們都只需要查詢至多一個塊中不同迴文子串的個數(記錄一些信息比如迴文自動機此時的指針,以及是否與預處理的答案有重複),效率就有保證了。

迴文自動機大佬可以無視下面的一大段

整理一下,我們需要實現兩個操作:1.前端插入。2.“記憶化”。這兩個其實翁文濤的論文裏都有提到,我就盜過來簡單分析一下。

前端插入

不妨在迴文樹上對每個節點再維護一個 fail 指針,表示這個節點的最長迴文前綴指向的節點,那麼每次加入新字符的時候,只需要沿着 s 的最長迴文前綴的 fail 鏈找到一個最長的合法的 t 即可。

……

所以,t 的迴文前綴與迴文後綴的字符串集合其實是相同的。也就是說,迴文樹上一個節點的 fail 指針,其實就是它的 fail 指針。

前端插入和末端插入一樣,我們需要找到最長迴文前綴。因爲每個節點都是迴文子串嘛……所以最長迴文前綴就是最長迴文後綴嘍……也就是說 fail 指針是一樣的。我們只需要多維護一個前端指針就行了。

需要注意在前(後)端插入時可能會對最長迴文後(前)綴造成影響,另外維護一下就好了。

這是什麼意思呢?我們會發現前端指針和末端指針一般是互不影響的,但一種情況除外,就是整個串是迴文串的情況,這時候前端指針和末端指針就變爲同一個節點了。

“記憶化”

首先我們會發現一個串的迴文自動機其實是可以給子串使用的,因爲子串的迴文自動機是該串迴文自動機的子圖(加點時間戳什麼的就可以同一個自動機用好多次啦)。但我們會發現如果每次還是用while去跑 fail 樹,複雜度會很不對,所以:

之前的插入算法的瓶頸在於,每次插入一個字符 c ,都要沿着當前最長迴文後綴 tfail 鏈往上找到第一個 v 使得 vs 中的前驅(即 v 的前一個字符)爲 c 。……因此,對於每個迴文樹中的節點 t ,另外維護一個失
配轉移數組 quick[c] ,存儲 t 的最長的滿足前驅爲 c 的迴文後綴。那麼在插入時,我們只需要首先檢查當前最長迴文後綴 t 的前綴是否爲 c ,假如不是,那麼合法的 v 直接就是 tquick[c]

(論文裏叫不基於勢能分析的插入算法,我感覺也很像記憶化QAQ)構造方法:

接下來考慮怎麼維護每個節點的 quick 。對於一個節點 ttquickfailtquick 幾乎沒有什麼差別,因爲 t 的迴文後綴只是在 failt 的迴文後綴中再加入了 failt 而已。首先將把 failtquick 複製一遍作爲 tquick ,令 cfailtt 中的前驅,用 failt 更新 tquick[c] 即可。直接暴力操作每次插入的時空複雜度都是 O(Σ)

示例程序

#include<cstdio>
#include<cctype>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=100000,maxs=317,maxi=26;

int tp,n,S,te,a[maxn+5],lstans;
int ans[maxs+5][maxn+5],p[maxs+5][maxn+5],pos[maxs+5][maxn+5];
int si,pb,pf,son[maxn+5][maxi],qui[maxn+5][maxi];
int ti,fai[maxn+5],len[maxn+5],vis[maxn+5];

#define Eoln(x) ((x)==10||(x)==13||(x)==EOF)
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF;return *l++;
}
inline int readi(int &x){
    int tot=0,f=1;char ch=readc(),lst='+';
    while (!isdigit(ch)) {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while (isdigit(ch)) tot=(tot<<3)+(tot<<1)+ch-48,ch=readc();
    return x=tot*f,Eoln(ch);
}
inline char getlwr() {char ch=readc();while (!islower(ch)) ch=readc();return ch;}
inline void Exback(int l,int i){
    int c=a[i];if (i-len[pb]-1<l||a[i-len[pb]-1]!=c) pb=qui[pb][c];
    if (!son[pb][c]){
        len[++si]=len[pb]+2;int k=fai[pb];
        if (a[i-len[k]-1]!=c) k=qui[k][c];k=son[k][c];
        memcpy(qui[si],qui[k],sizeof(qui[k]));
        qui[si][a[i-len[k]]]=k;fai[si]=k;son[pb][c]=si;
    }
    pb=son[pb][c];if (len[pb]==i-l+1) pf=pb;
}
inline void Exfront(int i,int r){
    int c=a[i];if (i+len[pf]+1>r||a[i+len[pf]+1]!=c) pf=qui[pf][c];
    if (!son[pf][c]){
        len[++si]=len[pf]+2;int k=fai[pf];
        if (a[i+len[k]+1]!=c) k=qui[k][c];k=son[k][c];
        memcpy(qui[si],qui[k],sizeof(qui[k]));
        qui[si][a[i+len[k]]]=k;fai[si]=k;son[pf][c]=si;
    }
    pf=son[pf][c];if (len[pf]==r-i+1) pb=pf;
}
#define ID(x) (((x)-1)/S+1)
int main(){
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    readi(tp);readi(n);readi(te);S=sqrt(n);
    for (int i=1;i<=n;i++) a[i]=getlwr()-'a';
    fai[0]=fai[1]=1;len[0]=0;len[1]=-1;si=1;
    for (int i=0;i<maxi;i++) qui[0][i]=1;
    memset(pos,63,sizeof(pos));
    for (int L=1,i=1;L<=n;L+=S,i++){
        pb=pf=0;ti++;
        for (int j=L;j<=n;j++){
            Exback(L,j);ans[i][j]=ans[i][j-1];p[i][j]=pf; //p[i][j]記錄前端指針
            if (vis[pb]<ti) vis[pb]=ti,pos[i][pb]=j,ans[i][j]++;
        }
    }
    while (te--){
        int L,R;readi(L);readi(R);
        if (tp) L^=lstans,R^=lstans;lstans=0;
        if (ID(L)==ID(R)){
            pb=0;ti++;
            for (int j=L;j<=R;j++)
                if (Exback(L,j),vis[pb]<ti)
                    vis[pb]=ti,lstans++;
        } else {
            int i=ID(L);lstans=ans[i+1][R];
            pf=p[i+1][R];ti++; //從之前記錄下來的p[i+1][R]開始前端插入
            for (int j=i*S;j>=L;j--)
                if (Exfront(j,R),vis[pf]<ti)
                    vis[pf]=ti,lstans+=pos[i+1][pf]>R; //pos[i+1][pf]>R判重
        }
        printf("%d\n",lstans);
    }
    return 0;
}
發佈了340 篇原創文章 · 獲贊 124 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章