其他分享
首页 > 其他分享> > [BZOJ 2865]字符串识别(后缀数组+线段树)

[BZOJ 2865]字符串识别(后缀数组+线段树)

作者:互联网

[BZOJ 2865]字符串识别(后缀数组+线段树)

题面

给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件:
1、i≤K≤j。
2、子串T只在S中出现过一次。

现在,给定S,XX希望知道对于S的每一位,最短的识别子串长度是多少.

\(|S| \leq 5 \times 10^5\)

 分析

引理:在字符串\(S\)的某个后缀\(i\)的所有前缀中,最短的识别子串的长度为\(\max(height[rank[i]],height[rank[i]+1])+1\)

证明: 感性理解,与后缀i字典序最接近的后缀,LCP长度一定更大。那么把这两个height取max,就得到了最长的LCP,再+1一定不会有重复的子串(否则height会更大).所以这一定是最短的识别子串

令\(len=\max(height[rank[i]],height[rank[i]+1])+1\),

那么这个识别子串对于\([i,i+len-1]\)的这几位的答案,一定有len的贡献,于是把\([i,i+len-1]\)的答案和len取min

并且,对于\(\forall j \in [i+len,n]\),子串\(s[i,j]\)一定是识别子串。因为若\(s[i,j]\)重复出现,那\(s[i,i+len-1]\)也一定重复出现,与引理矛盾。那么\([i+len,n]\)上的位置\(j\)的答案与\(j-i+1\)取min

第一种情况很好解决,用标记永久化的线段树区间修改即可。查询的时候把叶子节点到根上的所有标记取min即为答案。

第二种情况同理,只是我们只维护\(-i+1\)的最小值,查询第\(j\)个位置的时候先同情况一在线段树上查询,然后把结果加上\(j\)即可。

这样用两棵线段树维护两种情况的答案即可。时间复杂度\(O(n \log n)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring> 
#define INF 0x3f3f3f3f
#define maxn 500000
#define maxs 128 
using namespace std;
inline void qprint(int x){
    if(x<0){
        putchar('-');
        qprint(-x);
    }else if(x==0){
        putchar('0');
        return;
    }else{
        if(x>=10) qprint(x/10);
        putchar('0'+x%10);
    }
}
void rsort(int *ans,int *fi,int *se,int n,int m){
    static int buck[maxn+5];
    for(int i=0;i<=m;i++) buck[i]=0;
    for(int i=1;i<=n;i++) buck[fi[i]]++;
    for(int i=1;i<=m;i++) buck[i]+=buck[i-1];
    for(int i=n;i>=1;i--) ans[buck[fi[se[i]]]--]=se[i];
}
int sa[maxn+5],rk[maxn+5],height[maxn+5];
void suffix_sort(char *s,int n,int m){
    static int se[maxn+5];
    for(int i=1;i<=n;i++){
        rk[i]=s[i];
        se[i]=i;
    }
    rsort(sa,rk,se,n,m);
    for(int k=1;k<=n;k*=2){
        int p=0;
        for(int i=n-k+1;i<=n;i++) se[++p]=i;
        for(int i=1;i<=n;i++) if(sa[i]>k) se[++p]=sa[i]-k;
        rsort(sa,rk,se,n,m);
        swap(rk,se);
        rk[sa[1]]=1;
        p=1;
        for(int i=2;i<=n;i++){
            if(se[sa[i-1]]==se[sa[i]]&&se[sa[i-1]+k]==se[sa[i]+k]) rk[sa[i]]=p;
            else rk[sa[i]]=++p;
        }
        if(p==n) break;
        m=p;
    }
}
void get_height(char *s,int n,int m){
    suffix_sort(s,n,m);
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=n;i++){
        if(k) k--;
        int j=sa[rk[i]-1];
        while(s[i+k]==s[j+k]) k++;
        height[rk[i]]=k;
    }
}

struct segment_tree{
    struct node{
        int l;
        int r;
        int mark;//永久化标记    
    }tree[maxn*4+5];
    void build(int l,int r,int pos){
        tree[pos].l=l;
        tree[pos].r=r;
        tree[pos].mark=INF;
        if(l==r) return;
        int mid=(l+r)>>1;
        build(l,mid,pos<<1);
        build(mid+1,r,pos<<1|1);
    }
    void update(int L,int R,int val,int pos){
        if(L<=tree[pos].l&&R>=tree[pos].r){
            tree[pos].mark=min(tree[pos].mark,val);
            return;
        }
        //标记永久化,不用push_down
        int mid=(tree[pos].l+tree[pos].r)>>1;
        if(L<=mid) update(L,R,val,pos<<1);
        if(R>mid) update(L,R,val,pos<<1|1); 
    }
    int query(int qpos,int pos){
        if(tree[pos].l==tree[pos].r){
            return tree[pos].mark;
        }
        int mid=(tree[pos].l+tree[pos].r)>>1;
        if(qpos<=mid) return min(tree[pos].mark,query(qpos,pos<<1));
        else return min(tree[pos].mark,query(qpos,pos<<1|1));
    } 
}T1,T2;

int n;
char s[maxn+5];
int main(){
#ifdef LOCAL
//  freopen("1.in","r",stdin);
//  freopen("1.ans","w",stdout);
#endif
    scanf("%s",s+1);
    n=strlen(s+1);
    get_height(s,n,maxs);
    T1.build(1,n,1);
    T2.build(1,n,1);
    for(int i=1;i<=n;i++){
        int len=max(height[rk[i]],height[rk[i]+1])+1;
        if(i+len-1<=n) T1.update(i,i+len-1,len,1);
        if(i+len-1<n) T2.update(i+len,n,-i+1,1);//第j位的答案是j-i+1,不存储j,只存储min(-i+1) 
    } 
    for(int i=1;i<=n;i++){
        qprint(min(T1.query(i,1),T2.query(i,1)+i));
        putchar('\n'); 
    }
} 

标签:子串,2865,后缀,pos,len,height,int,se,BZOJ
来源: https://www.cnblogs.com/birchtree/p/12246503.html