1. 程式人生 > >[BJWC2018]Border 的四種求法(字尾自動機+鏈分治+線段樹合併)

[BJWC2018]Border 的四種求法(字尾自動機+鏈分治+線段樹合併)

題目描述

給一個小寫字母字串 S ,q 次詢問每次給出 l,r ,求 s[l..r] 的 Border 。

Border: 對於給定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 為 s 的長度。

題解

這題的描述很短,給人一種很可做的假象。

暴力1:每次對區間lr做一次KMP,求出border陣列,複雜度nq。

暴力2:構建字尾自動機,用線段樹合併維護出right集合考慮到兩個串的最長字尾為他們在parent樹上的LCA的len,所以我們可以在parent樹上跳father,相當於列舉LCA,那麼如果有匹配的點,則一定滿足:

i-len[LCA]+1<=l => i=len[LCA]<l => i<l+len[LCA]       i>=l&&i<r (i為我們要求的點,l為詢問的左端點,LCA為i和r的LCA)

所以我們只需要線上段樹上查子樹內查詢滿足上述條件的最大的i就可以了,複雜度最好qlogn,最差qn。

這兩種暴力好像差不多。。

我們觀察到第二種暴力它的瓶頸在於列舉LCA,但查詢只需要一個log,我們可以想一些辦法把複雜度均攤一下。

鏈分治

這就是這道題的重頭戲,它用到了一個重要的性質,我們將一棵樹重鏈剖分之後,從根到任意一點的路徑上,輕重鏈切換的次數是不超過log的。

然後我們就可以用它搞一些事情。

比如說這道題,我們可以把詢問拆成log個掛在它到根的路徑上,每條鏈掛一個,對於當前鏈,掛在原來的點上,對於上面的鏈,掛在鏈的最底端。

其實我們也是相當於在列舉lca,對於掛上去的點,我們可以直接用上面暴力2的方法統計答案,複雜度是log的,那麼對於其他點,我們如何統計答案呢?

我們可以發現一個性質,就是對於最底端這個點向上的其他鏈上的點作為LCA時,答案只會出現在這個點除了重兒子以外的子樹內以及自己,因為重鏈底下的子樹我們剛才已經處理過了。

所以我們的做法就是,對於每一條重鏈,自頂至底處理,然後我們把式子移個項

i-len[LCA]<l

我們把這個點的所有非重兒子所在子樹中的點的i-len[LCA]全部加進以i為下標的線段樹中,然後一直往下合併,那麼每到一個點,它就儲存了它到重鏈頂端所有LCA的資訊,然後我們直接詢問掛在這個點的詢問就可以了。

但每次暴力加的複雜度對嗎?

樹上啟發式合併(DSU on tree)

這個操作的原理和上面的一模一樣,都是利用了輕重鏈log的性質。

主要思想就是,維護重鏈,輕鏈暴力,考慮一個點會被暴力做多少次,它到根的路徑上輕重鏈切換的時候才會產生一次,根據上面的結論,這是log的。

於是我們在nlog2的時間內做完了這道題。

程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 400002
#define inf 2e9
using namespace std;
vector<int>vec[N];
char s[N];
int last,cnt,n,tot,head[N],topp,ls[N*30],rs[N*30],tr[N*30],T[N<<1],len[N],id[N],ch[N][26],fa[N],f[N];
int L[N*30],R[N*30],size[N],top[N],son[N],ans[N],q,rt[N],_id[N],mi[N*30];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N<<1];
struct node{int l,r;}qu[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline int newnode(){
    int x=++topp;tr[x]=inf;ls[x]=rs[x]=0;
    return x;
}
void ins(int &cnt,int l,int r,int x){
    if(!cnt)cnt=++topp,mi[cnt]=inf;mi[cnt]=min(mi[cnt],x);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins(L[cnt],l,mid,x);
    else ins(R[cnt],mid+1,r,x);
}
int merge(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int p=++topp;mi[p]=min(mi[cnt],mi[pre]);
    if(l==r)return p;
    int mid=(l+r)>>1;
    L[p]=merge(L[cnt],L[pre],l,mid);
    R[p]=merge(R[cnt],R[pre],mid+1,r);
    return p;
}
int prequery(int cnt,int l,int r,int ql,int qr,int x){
    if(mi[cnt]>=x)return 0;
    if(l==r)return (l>=ql&&l<=qr)?l:0;
    int mid=(l+r)>>1;
    if(mid<qr&&mi[R[cnt]]<x){
        int num=prequery(R[cnt],mid+1,r,ql,qr,x);
        if(num)return num;
    }
    else if(mid>=ql&&mi[L[cnt]]<x)return prequery(L[cnt],l,mid,ql,qr,x);
    return 0;
}
inline void insert(int x,int tag){
    int p=last,np=++cnt;last=np;len[np]=len[p]+1;
    ins(T[np],1,n,tag);id[tag]=np;_id[np]=tag;
    for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
    if(!p)fa[np]=1;
    else{
        int q=ch[p][x];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
        }
    }
}
void ins2(int &cnt,int l,int r,int x,int y){
    if(!cnt)cnt=newnode();tr[cnt]=min(tr[cnt],y);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins2(ls[cnt],l,mid,x,y);
    else ins2(rs[cnt],mid+1,r,x,y);
}
int merge2(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int mid=(l+r)>>1;tr[cnt]=min(tr[cnt],tr[pre]);
    if(l==r)return cnt;
    ls[cnt]=merge2(ls[cnt],ls[pre],l,mid);
    rs[cnt]=merge2(rs[cnt],rs[pre],mid+1,r);
    return cnt;
}
int query(int cnt,int l,int r,int L,int R,int x){   ///!!!
    if(l==r)return (l>=L&&l<=R)?l:0;
    if(tr[cnt]>=x)return 0;
    int mid=(l+r)>>1;
    if(mid<R&&tr[rs[cnt]]<x){
        int num=query(rs[cnt],mid+1,r,L,R,x);
        if(num)return num;
    } 
    if(tr[ls[cnt]]<x&&mid>=L)return query(ls[cnt],l,mid,L,R,x);
    return 0;
}
void dfs1(int u){
    size[u]=1;
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;
        f[v]=u;dfs1(v);
        size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}
void dfs2(int u){
    if(!top[u])top[u]=u;if(son[u])top[son[u]]=top[u],dfs2(son[u]);
    for(int i=head[u];i;i=e[i].n){
       if(e[i].to!=son[u])dfs2(e[i].to);
       T[u]=merge(T[u],T[e[i].to],1,n);
    }
} 
void prework(int cnt,int l,int r,int top){
    if(l==r){ins2(rt[top],1,n,l,l-len[top]);return;}
    int mid=(l+r)>>1;
    if(L[cnt])prework(L[cnt],l,mid,top);
    if(R[cnt])prework(R[cnt],mid+1,r,top);
}
void solve(int u){
    for(int i=head[u];i;i=e[i].n)solve(e[i].to);
    if(top[u]==u){
        int x=u; 
        while(x){    
            for(int i=head[x];i;i=e[i].n){
              int v=e[i].to;if(v==son[x])continue;
              prework(T[v],1,n,x);
            }
            for(int i=0;i<vec[x].size();++i){
                int l=qu[vec[x][i]].l,r=qu[vec[x][i]].r;
                ans[vec[x][i]]=max(ans[vec[x][i]],query(rt[x],1,n,l,r-1,l));
            }
            if(son[x])rt[son[x]]=merge2(rt[son[x]],rt[x],1,n);
            x=son[x];
        }
    }
}
int main(){
    tr[0]=mi[0]=inf;
    scanf("%s",s+1);n=strlen(s+1);
    cnt=last=1;
    for(int i=1;i<=n;++i)insert(s[i]-'a',i);
    for(int i=2;i<=cnt;++i)add(fa[i],i);
    dfs1(1);dfs2(1);
    q=rd();topp=0;//合併完後的第一類線段樹不會新增節點了,所以把它清空。 
    for(int i=1;i<=q;++i){
        qu[i].l=rd();qu[i].r=rd();int x=id[qu[i].r];
        while(x){
          vec[x].push_back(i);
          ans[i]=max(ans[i],prequery(T[x],1,n,qu[i].l,qu[i].r-1,qu[i].l+len[x]));//!!!
          x=f[top[x]];
        }
    }
    for(int i=1;i<=n;++i)ins2(rt[id[i]],1,n,i,0);
    solve(1);
    for(int i=1;i<=q;++i)printf("%d\n",ans[i]?ans[i]-qu[i].l+1:0);
    return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 400002
#define inf 2e9
using namespace std;
vector<int>vec[N];
char s[N];
int last,cnt,n,tot,head[N],topp,ls[N*30],rs[N*30],tr[N*30],T[N<<1],len[N],id[N],ch[N][26],fa[N],f[N];
int L[N*30],R[N*30],size[N],top[N],son[N],ans[N],q,rt[N],_id[N],mi[N*30];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N<<1];
struct node{int l,r;}qu[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline int newnode(){
    int x=++topp;tr[x]=inf;ls[x]=rs[x]=0;
    return x;
}
void ins(int &cnt,int l,int r,int x){
    if(!cnt)cnt=++topp,mi[cnt]=inf;mi[cnt]=min(mi[cnt],x);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins(L[cnt],l,mid,x);
    else ins(R[cnt],mid+1,r,x);
}
int merge(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int p=++topp;mi[p]=min(mi[cnt],mi[pre]);
    if(l==r)return p;
    int mid=(l+r)>>1;
    L[p]=merge(L[cnt],L[pre],l,mid);
    R[p]=merge(R[cnt],R[pre],mid+1,r);
    return p;
}
int prequery(int cnt,int l,int r,int ql,int qr,int x){
    if(mi[cnt]>=x)return 0;
    if(l==r)return (l>=ql&&l<=qr)?l:0;
    int mid=(l+r)>>1;
    if(mid<qr&&mi[R[cnt]]<x){
        int num=prequery(R[cnt],mid+1,r,ql,qr,x);
        if(num)return num;
    }
    else if(mid>=ql&&mi[L[cnt]]<x)return prequery(L[cnt],l,mid,ql,qr,x);
    return 0;
}
inline void insert(int x,int tag){
    int p=last,np=++cnt;last=np;len[np]=len[p]+1;
    ins(T[np],1,n,tag);id[tag]=np;_id[np]=tag;
    for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
    if(!p)fa[np]=1;
    else{
        int q=ch[p][x];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
        }
    }
}
void ins2(int &cnt,int l,int r,int x,int y){
    if(!cnt)cnt=newnode();tr[cnt]=min(tr[cnt],y);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins2(ls[cnt],l,mid,x,y);
    else ins2(rs[cnt],mid+1,r,x,y);
}
int merge2(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int mid=(l+r)>>1;tr[cnt]=min(tr[cnt],tr[pre]);
    if(l==r)return cnt;
    ls[cnt]=merge2(ls[cnt],ls[pre],l,mid);
    rs[cnt]=merge2(rs[cnt],rs[pre],mid+1,r);
    return cnt;
}
int query(int cnt,int l,int r,int L,int R,int x){   ///!!!
    if(l==r)return (l>=L&&l<=R)?l:0;
    if(tr[cnt]>=x)return 0;
    int mid=(l+r)>>1;
    if(mid<R&&tr[rs[cnt]]<x){
        int num=query(rs[cnt],mid+1,r,L,R,x);
        if(num)return num;
    } 
    if(tr[ls[cnt]]<x&&mid>=L)return query(ls[cnt],l,mid,L,R,x);
    return 0;
}
void dfs1(int u){
    size[u]=1;
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;
        f[v]=u;dfs1(v);
        size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}
void dfs2(int u){
    if(!top[u])top[u]=u;if(son[u])top[son[u]]=top[u],dfs2(son[u]);
    for(int i=head[u];i;i=e[i].n){
       if(e[i].to!=son[u])dfs2(e[i].to);
       T[u]=merge(T[u],T[e[i].to],1,n);
    }
} 
void prework(int cnt,int l,int r,int top){
    if(l==r){ins2(rt[top],1,n,l,l-len[top]);return;}
    int mid=(l+r)>>1;
    if(L[cnt])prework(L[cnt],l,mid,top);
    if(R[cnt])prework(R[cnt],mid+1,r,top);
}
void solve(int u){
    for(int i=head[u];i;i=e[i].n)solve(e[i].to);
    if(top[u]==u){
        int x=u; 
        while(x){    
            for(int i=head[x];i;i=e[i].n){
              int v=e[i].to;if(v==son[x])continue;
              prework(T[v],1,n,x);
            }
            for(int i=0;i<vec[x].size();++i){
                int l=qu[vec[x][i]].l,r=qu[vec[x][i]].r;
                ans[vec[x][i]]=max(ans[vec[x][i]],query(rt[x],1,n,l,r-1,l));
            }
            if(son[x])rt[son[x]]=merge2(rt[son[x]],rt[x],1,n);
            x=son[x];
        }
    }
}
int main(){
    tr[0]=mi[0]=inf;
    scanf("%s",s+1);n=strlen(s+1);
    cnt=last=1;
    for(int i=1;i<=n;++i)insert(s[i]-'a',i);
    for(int i=2;i<=cnt;++i)add(fa[i],i);
    dfs1(1);dfs2(1);
    q=rd();topp=0;//合併完後的第一類線段樹不會新增節點了,所以把它清空。 
    for(int i=1;i<=q;++i){
        qu[i].l=rd();qu[i].r=rd();int x=id[qu[i].r];
        while(x){
          vec[x].push_back(i);
          ans[i]=max(ans[i],prequery(T[x],1,n,qu[i].l,qu[i].r-1,qu[i].l+len[x]));//!!!
          x=f[top[x]];
        }
    }
    for(int i=1;i<=n;++i)ins2(rt[id[i]],1,n,i,0);
    solve(1);
    for(int i=1;i<=q;++i)printf("%d\n",ans[i]?ans[i]-qu[i].l+1:0);
    return 0;
}