后缀数组

具体讲解:点击此处查看

模板:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int maxn=20000+1000;
struct SuffixArray
{
    //保存原始字符串+‘\0’后形成的字符串
    //即原始字符串在s中表示[0,n-2]范围,
    //然后s[n-1]其实是人为添加的'\0'字符
    char s[maxn];
 
    //排名(后缀)数组,sa[i]==j表示字典序为i的后缀是后缀j
    //其中i从0到n-1,j从0到n-1范围
    int sa[maxn];
 
    //名次数组,rank[i]==j表示后缀i的字典序排名为j
    int rank[maxn];
    int height[maxn];
 
    //辅助数组用于x和y数组
    int t1[maxn],t2[maxn];
 
    //c[i]==j表示关键字<=i的关键字有j个
    int c[maxn];
 
    //s原始字符串+‘\0'字符后的长度
    //由于添加了尾0,所以n一般都>=2的
    int n;//n>=2,不能等于1,否则build_height()函数可能会出BUG
 
    //m大于s[]数组出现的任意字符的int值
    void build_sa(int m)
    {
        int i,*x=t1,*y=t2;
 
        //预处理每个后缀的长度为1的前缀,求出x数组和sa数组
        //此时x[i]==j表示第i个字符的绝对值(可以看成是名次数组)
        //但有可能x[1]=2,且x[3]=2,说明1字符和3字符完全一样。
        //此时算出的sa[i]==j表示当前长度为1的字符串的排名数组,
        //排名数组值不会一样
        //就算x[1]==x[3]==2,但是sa[1]=1,而sa[2]=3。
        //即就算1号字符和3号字符是完全一样的,
        //但是排名时第1名是1号字符,第2名才是3号字符
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[i]=s[i]]++;
 
        //此时c[i]表示关键字<=i的关键字一共有c[i]个
        for(i=1;i<m;i++) c[i]+=c[i-1];
 
        //计算当前长度(1)的排名数组
        for(i=n-1;i>=0;i--) sa[--c[x[i]]] = i;
 
 
        //每轮循环开始前我们通过之前的计算得到了x[]和sa[]:
        //每个后缀的长为k的前缀(即每个后缀的前[0,k-1]字符)的名次数组x[],
        //我们还知道每个后缀的长为k的前缀的排名数组sa[],
        //然后通过sa[]数组我们可以求得每个后缀的第[k,2*k-1]字符的排名数组y[],
        //然后通过k字符的x[]与k字符的y[],
        //我们可以求得每个后缀的长为2k的前缀字符串的sa[]排名数组
        //然后通过该sa[]排名数组,和k字符的x数组,我们可以求得2k字符的x[]数组
        //以上每轮的x[]名次数组都是可能有重复值大小的,但是sa[]值不会重复
        //比如表示k个字符的x[1]=2,x[4]=2时,
        //那么表示[1,k+1]字符串与[4,k+4]字符完全相同,且排名为2(最高排名为0)
        //当哪轮求出的x[]数组正好由n个值(即所有值都不重复时)
        //说明所有后缀已经排序完毕
        for(int k=1;k<=n;k<<=1)
        {
            //先计算每个后缀的前缀的[k,2*k-1]字符的排名数组y
            //即y是每个后缀的长为2k前缀的第二关键字
            int p=0;
 
            //y[p]==i表第二关键字为第p名的是后缀i
            //由于当前处理的是每个后缀的前缀的[k,2*k-1]字符
            //而后缀n-k到后缀n-1不存在第k个字符(想想是不是)
            //所以他们的第二关键字的名字自然优先
            for(i=n-k;i<n;i++) y[p++]=i;
 
            //除了上面那些后缀不存在第二关键字
            //x+k后缀的第1关键字排名-k 等于 x后缀的第2关键字排名
            for(i=0;i<n;i++)if(sa[i]>=k) y[p++]=sa[i]-k;
 
            //上面已经计算出了y[],(x[]数组上一轮已经算出)
            //下面通过第1关键字x[]名次数组和第2关键字y[]排名数组
            //计算综合后每个后缀的长2k前缀的sa[]数组
            for(i=0;i<m;i++) c[i]=0;
            for(i=0;i<n;i++) c[x[y[i]]]++;
            for(i=1;i<m;i++) c[i]+=c[i-1];
            for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]] = y[i];
 
            //交换x和y,令y表示名次数组
            //计算综合后每个后缀的长2k前缀的x[]数组
            swap(x,y);
            //此时p用来记录x[]数组中不同值的个数
            p=1;x[sa[0]]=0;
            for(i=1;i<n;i++)
                x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p-1:p++;
                //上面y[sa[i]+k]的sa[i]+k<=n-1,因为只要然后两个不同的后缀必然要分出大小
                //所以在他们y[sa[i]]==y[sa[i-1]],即这两个后缀的长k的第一关键字相同的情况下
                //他们必定还存在第二关键需要比较
            if(p>=n) break;
            m=p;
        }
    }
 
    //此函数详解见刘汝佳<<训练指南>>P222
    //height[i]表示sa[i-1]后缀与sa[i]后缀的最大公共前缀长度
    //即表示排名i-1和排名i的后缀的最大公共前缀LCP长度
    //所以height数组只有[1,n-1]是有效下标
    void build_height()//n不能等于1,否则出BUG
    {
        int i,j,k=0;
        for(i=0;i<n;i++)rank[sa[i]]=i;
        for(i=0;i<n;i++)
        {
            if(k)k--;
            j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++;
            height[rank[i]]=k;
        }
    }
}sa;
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000000+100;
struct SuffixArray
{
    char s[maxn];
    int sa[maxn],rank[maxn],height[maxn];
    int t1[maxn],t2[maxn],c[maxn],n;
    int dmin[maxn][20];
    void build_sa(int m)
    {
        int i,*x=t1,*y=t2;
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[i]=s[i]]++;
        for(i=1;i<m;i++) c[i]+=c[i-1];
        for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int p=0;
            for(i=n-k;i<n;i++) y[p++]=i;
            for(i=0;i<n;i++)if(sa[i]>=k) y[p++]=sa[i]-k;
            for(i=0;i<m;i++) c[i]=0;
            for(i=0;i<n;i++) c[x[y[i]]]++;
            for(i=1;i<m;i++) c[i]+=c[i-1];
            for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]] = y[i];
            swap(x,y);
            p=1,x[sa[0]]=0;
            for(i=1;i<n;i++)
                x[sa[i]]= y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]? p-1:p++;
            if(p>=n) break;
            m=p;
        }
    }
    void build_height()//n不能等于1,否则出BUG
    {
        int i,j,k=0;
        for(i=0;i<n;i++)rank[sa[i]]=i;
        for(i=0;i<n;i++)
        {
            if(k)k--;
            j=sa[rank[i]-1];
            while(s[i+k]==s[j+k])k++;
            height[rank[i]]=k;
        }
    }
    void initMin()
    {
        for(int i=1;i<=n;i++) dmin[i][0]=height[i];
        for(int j=1;(1<<j)<=n;j++)
            for(int i=1;i+(1<<j)-1<=n;i++)
                dmin[i][j]=min(dmin[i][j-1] , dmin[i+(1<<(j-1))][j-1]);
    }
    int RMQ(int L,int R)//取得范围最小值
    {
        int k=0;
        while((1<<(k+1))<=R-L+1)k++;
        return min(dmin[L][k] , dmin[R-(1<<k)+1][k]);
    }
    int LCP(int i,int j)//求后缀i和j的LCP最长公共前缀
    {
        int L=rank[i],R=rank[j];
        if(L>R) swap(L,R);
        L++;//注意这里
        return RMQ(L,R);
    }
}sa;

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值