后缀数组题目小结。

学习参考https://wenku.baidu.com/view/228caa45b307e87101f696a8.html
http://blog.csdn.net/yxuanwkeith/article/details/50636898

4个比较基础的应用

Q1:一个串中两个串的最大公共前缀是多少?
A1:这不就是Height吗?用rmq预处理,再O(1)查询。

Q2:一个串中可重叠的重复最长子串是多长?
A2:就是求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值,那最长的就是Height中的最大值。

Q3:一个串种不可重叠的重复最长子串是多长?
A3:先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的Height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组超过的话k就是合法答案。

A4:一个字符串不相等的子串的个数是多少?
Q4:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] + 1,但是有子串算重复,重复的就是Heigh[i]个与前面相同的前缀,那么减去就可以了。最后,一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] + 1 - Height[k]

贴个完整的
POJ - 1743 https://vjudge.net/contest/184489#problem/B

 题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:

    1.长度至少为5个音符。

    2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)

    3.重复出现的同一主题不能有公共部分。


int sa[N],wv[N],wx[N],wa[N],wb[N];
int cmp(int *r,int a,int b,int l){
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int n,int m){
    int *x=wa,*y=wb;
    for(int i=0;i<m;++i)wx[i]=0;
    for(int i=0;i<n;++i)wx[x[i]=r[i]]++;
    for(int i=1;i<m;++i)wx[i]+=wx[i-1];
    for(int i=n-1;i>=0;--i)sa[--wx[x[i]]]=i;
    for(int j=1,p=1;p<n;j<<=1,m=p){
        p=0;
        for(int i=n-j;i<n;++i)y[p++]=i;
        for(int i=0;i<n;++i)if(sa[i]>=j)y[p++]=sa[i]-j;
        for(int i=0;i<n;++i)wv[i]=x[y[i]];
        for(int i=0;i<m;++i)wx[i]=0;
        for(int i=0;i<n;++i)wx[wv[i]]++;
        for(int i=1;i<m;++i)wx[i]+=wx[i-1];
        for(int i=n-1;i>=0;--i)sa[--wx[wv[i]]]=y[i];
        swap(x,y),x[sa[0]]=0,p=1;
        for(int i=1;i<n;++i)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
int rak[N],h[N];
void calc_h(int *r,int n){
    for(int i=1;i<=n;++i)rak[sa[i]]=i;
    for(int i=0,k=0;i<n;++i){
        if(k)--k;
        int j=sa[rak[i]-1];
        while(r[i+k]==r[j+k])++k;
        h[rak[i]]=k;
    }
}
int n;
bool check(int m){
    int mx=-inf,mi=inf;
    for(int i=0;i<n;++i){
        if(h[i]<m)
            mi=mx=sa[i];
        else{
            mi=min(mi,min(sa[i],sa[i-1]));
            mx=max(mx,max(sa[i],sa[i-1]));
            if(mx-mi>=m)return 1;
        }
    }
    return 0;
}
int a[N];
int main(){
    while(~sf("%d",&n)&&n){
        rep(i,0,n-1)sf("%d",&a[i]);
        rep(i,0,n-2)a[i]=a[i+1]-a[i];
        for(int i=0;i<n-1;++i)a[i]+=100;
        a[n-1]=0;
        da(a,n,200);
        n--;
        calc_h(a,n);
        int l=0,r=n;
        while(l<r-1){
            int mid=(r+l)>>1;
            if(!check(mid))r=mid;
            else l=mid;
        }
        if(l<4)puts("0");
        else pf("%d\n",l+1);
    }
}

题意:给你两串字符,要你找出在这两串字符中都出现过的最长子串.........
char s1[N],s2[N];
int main(){
    while(~sf("%s",s1)){
        int n=strlen(s1);
        sf("%s",s2);int m=strlen(s2);
        strcpy(s1+n+1,s2);
        s1[n]='@';
        int tot=n+1+m;
        da(s1,tot+1,130);
        calc_h(s1,tot);
        int ans=0;
        for(int i=1;i<=tot;++i){
            if(h[i]>=ans){
                if(sa[i-1]<n&&sa[i]>n)
                    ans=h[i];
                if(sa[i-1]>n&&sa[i]<n)
                    ans=h[i];
            }
        }
        pf("%d\n",ans);
    }
}

#三:

题意:找出出现k次的可重叠的最长子串的长度

思路:其实通过做几道后缀数组,那么这道题就是模板水题了,通过二分长度,然后分成若干组去寻找答案

int s[N];
int n,k;
int mx;
bool ok(int mid){
    int num=0;
    for(int i=2;i<=n;++i){
        if(h[i]>=mid){//这个地方注意是>=mid,如果是==的话会错
            num++;
        }
        else if(h[i]<mid){
            num=1;
        }
        if(num>=k)return 1;//也是>=
    }
    return false;
}
void solve(){
    int l=0,r=n;
    while(l<=r){
        int mid=(l+r)>>1;
        //cout<<mid<<'\n';
        if(ok(mid))l=mid+1,ans=mid;
        else r=mid-1;
    }
}
int main(){
    //ree
    while(~sf("%d %d",&n,&k)){
        mx=0;
        rep(i,0,n-1){sf("%d",&s[i]);mx=max(mx,s[i]);}
        s[n]=0;
        da(s,n+1,mx+1);
        calc_h(s,n);
        solve();
        pf("%d\n",ans);
    }
}

四:



    公共子串:

    给出两个字符串A,B,求满足下列条件的C的个数:

        C是A的子串

        C也是B的子串

        C加上其在A中的后继字符(若在最尾则加上空格字符)不能够在B中出现

参考http://blog.csdn.net/acm_cxlove/article/details/7952269
将两个串拼接后,求出后缀数组

由于题目要求两个后缀的LCP恰好为K。

可以求出大于等于K的减去大于等于K+1的有多少个。这个常见套路

利用height将后缀分组,然后统计每一组内第一个串和第二个串分别有多少

int s[N];
int n,m,k;
int mx;
int a[N];
LL solve(int num,int len){
    int A=0,B=0;
    LL ans=0;
    for(int i=0;i<len;++i){
        if(h[i]<num){
            if(B){ ans+=A; }
            A=0;B=0;
        }
        if(sa[i+1]<n)A++;
        else B++;
    }
    return ans;
}
int main(){
    ree
    while(~sf("%d%d%d",&n,&m,&k)){
        int mx=0;
        rep(i,0,n-1){sf("%d",&a[i]);mx=max(mx,a[i]);}
        int tot=n+m+1;
        rep(i,n+1,n+m){sf("%d",&a[i]);mx=max(mx,a[i]);}
        a[n]=0;
        da(a,tot,mx+1);
        calc_h(a,tot);
        cout<<solve(k,tot)-solve(k+1,tot)<<'\n';
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值