后缀数组学习与应用

学习步骤

首先明确几个概念:

sa[i]:即,排名为i的后缀的起点下标
rank[i]:即,第i个后缀的排名
height[i]:即,sa[i]和sa[i-1]的最长公共前缀
h[i]:即,height[rank[i]],即第i个后缀与前一名的最长公共前缀

一些结论

结论一:
定义: LCP(i,j)=lcp(suffix(sa[i],suffix(sa[j]) L C P ( i , j ) = l c p ( s u f f i x ( s a [ i ] , s u f f i x ( s a [ j ] )
则转化: LCP(i,j)=min(height[k]) L C P ( i , j ) = m i n ( h e i g h t [ k ] ) ,sa[i]+1≤k≤sa[j]
结论二:
suffix[i]的与其他后缀的最长公共前缀为 max(height[sa[i]],height[sa[i]+1]) m a x ( h e i g h t [ s a [ i ] ] , h e i g h t [ s a [ i ] + 1 ] )
结论三:
h[i] h [ i ] h[i1]1 h [ i − 1 ] − 1 ,理由suffix[i-1]去掉一个字符后的suffix[i],必然存在另一个后缀也去掉相同字符后的的匹配长度为h[i-1]-1;由结论二可知, h[i] h [ i ] h[i1]1 h [ i − 1 ] − 1 ;

了解一下后缀排序过程

这里写图片描述

其中x为上一次排序的rank,而y为第二关键字的rank,两者组合后的排序为本次的rank

贴一份好博客

poj2774
题意:问两个字符串的最长公共子串
题解:两个字符串拼接,中间用特殊字符隔开,然后后缀数组模版,详见代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=200005;
int a[maxn];//
int sa[maxn],height[maxn],rank[maxn],tax[maxn],y[maxn];
//sa[i]排名为i的后缀的起始下标;rank[i]起始下标为i的后缀的排名;
//height[i]排名为i的后缀和前一名的lcp;tax[i]辅助计数排序,记录的是排名为i的个数,它维护一个前缀
//y[i]维护的是第二关键字 ,表示排名为i的是那个第二关键字与第y[i]个第一关键字组合排序
int n,m;//n为字符串长度,m为ascii码个数,也是排名种数。

void rsort() {
    //rank为第一关键字,y为第二关键字

    for(int i=0; i<=m; i++)tax[i]=0;
    for(int i=1; i<=n; i++)tax[rank[y[i]]]++; //用第一关键字对第二关键字的排名进行离散化
    for(int i=1; i<=m; i++)tax[i]+=tax[i-1]; //tax[i]表示的是排名
    for(int i=n; i>=1; i--)sa[tax[rank[y[i]]]--]=y[i];
}
int cmp(int *f, int x, int y, int w) {
    return f[x] == f[y] && f[x + w] == f[y + w];
}

void suffix() {
    for(int i=1; i<=n; i++)rank[i]=a[i],y[i]=i;
    m=128;
    rsort();//rsort后得到本轮排序的sa

    for(int w=1,p=1,i; p<n; w+=w,m=p) { //p表示本轮排序的rank总类数,当有n种rank时,即p=n,排序结束
        for (p = 0, i = n - w + 1; i <= n; i ++) y[++ p] = i; //长度越界,第二关键字为0
        for (i = 1; i <= n; i ++) if (sa[i] > w) y[++ p] = sa[i] - w;
        //由计数排序的操作过程可知,sa的前w个后缀在第二关键字中被移除,而后溢出的补0

        rsort();
        swap(y,rank);//上一轮的rank放到y中保存一下,用于计算下一轮的rank;

        rank[sa[1]]=p=1;
        for(i=2; i<=n; i++)rank[sa[i]]=cmp(y,sa[i],sa[i-1],w)?p:++p;
        //通过观察上一轮的rank,比较当前排名为i和i-1的rank是否一样,如果一样则rank相等,否则新加一个rank

    }
    //计算lcp
    int k=0,j;
    for(int i=1; i<=n; height[rank[i++]]=k)
        for(k=(k?k-1:k),j=sa[rank[i]-1]; (i+k)<=n&&(j+k)<=n&&a[i+k]==a[j+k]; k++);
    //通过我们的结论h[i]>=h[i-1]-1;可知我们设k=h[i-1]-1;那么下一次匹配时只需从 a[i+k]==a[j+k]开始比较

}

char str[maxn],str1[maxn];
int l1,l2;
void init() {
    scanf("%s %s",str,str1);
    l1=strlen(str);
    l2=strlen(str1);
    for(int i=1; i<=l1; i++) {
        a[i]=str[i-1];
    }
    a[l1+1]=128;
    for(int i=0; i<l2; i++) {
        a[i+l1+2]=str1[i];
    }
    n=l1+l2+1;  
}
int main() {
    int ans=0;
    init();
    suffix();

    for(int i=2; i<=n; i++) {
        if(ans<height[i]) {
            int x = min(sa[i], sa[i - 1]);
            int y = max(sa[i], sa[i - 1]);
            if((x<l1+1)&&(y>l1+1))
                ans=max(height[i],ans);
        }
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值