后缀数组课堂笔记

· · 个人记录

自己的模板

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int N=2000010;
char s[N];
int n,m,sa[N],x[N],y[N],c[N],ex[N];

void getSA(){
    //注意此处x是第一关键字,y是第二关键字,但x是映射,y是排序 
    //处理出预制(k=1)的sa和x,其中m为映射总量 
    m=122;
    for(int i=1;i<=n;i++){
        x[i]=s[i];
        c[x[i]]++;
    }
    for(int i=2;i<=m;i++){
        c[i]+=c[i-1];
    }
    for(int i=1;i<=n;i++){
        sa[++c[x[i]-1]]=i;
    }
    /*
    for(int i=1;i<=n;i++){
        printf("%d ",sa[i]);
    }
    */
    for(int k=1;k<=n;k<<=1){
        //通过后缀性质求出y 
        int e=0;
        for(int i=n-k+1;i<=n;i++){
            y[++e]=i;
        }
        for(int i=1;i<=n;i++){
            if(sa[i]>k)y[++e]=sa[i]-k;
        }
        /*
        for(int i=1;i<=n;i++){
            printf("%d ",y[i]);
        }
        printf("\n");
        */

        //进行基数排序把k*2情况下的sa求出 
        for(int i=0;i<=m;i++){
            c[i]=0;
        }
        for(int i=1;i<=n;i++){
            c[x[i]]++;
        }
        for(int i=2;i<=m;i++){
            c[i]+=c[i-1];
        }
        for(int i=1;i<=n;i++){
            sa[++c[x[y[i]]-1]]=y[i];
        }
        /*
        for(int i=1;i<=n;i++){
            printf("%d ",sa[i]);
        }
        printf("\n");
        */
        //处理出新的映射 
        ex[sa[1]]=1;m=1;//sa[1]的映射设为1 
        for(int i=2;i<=n;i++){
            if(x[sa[i]]==x[sa[i-1]]&&x[sa[i]+k]==x[sa[i-1]+k]){
                //一二关键字相同应该是同个映射(对于空格情况x[>n]=0,仍可判断)
                ex[sa[i]]=m;
            }
            else{
                ex[sa[i]]=++m;
            }
        }
        for(int i=1;i<=n;i++){
            x[i]=ex[i];
        }
        if(m==n)break;
    }
}

int main(){
    scanf("%s",s+1);
    n=strlen(s+1);

    getSA();

    for(int i=1;i<=n;i++){
        printf("%d ",sa[i]);
    }
}

老师的模板

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 1000050
using namespace std;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],wt[30];
int n,m;
void putout(int x) {
    if(!x) {
        putchar(48);
        return;
    }
    int l=0;
    while(x) wt[++l]=x%10,x/=10;
    while(l) putchar(wt[l--]+48);
}
void  get_SA() {
    //对长度为1的字符串排序
    //一般来说,在字符串的题目中,r的最大值不会很大,所以这里使用了基数排序
    //如果r的最大值很大,那么把这段代码改成快速排序
    for (int i=1; i<=n; ++i) ++c[x[i]=s[i]];
    //c数组是桶,x[i]是第i个元素的第一关键字
    for (int i=2; i<=m; ++i) c[i]+=c[i-1];
    //做c的前缀和,我们就可以得出每个关键字最多是在第几名
    for (int i=n; i>=1; --i) sa[c[x[i]]--]=i;//基数排序,字符排名 
    for (int k=1; k<=n; k<<=1) {
        int num=0;
        for (int i=n-k+1; i<=n; ++i) y[++num]=i;
        //y[i]表示第二关键字排名为i的数,第一关键字的位置
        //第n-k+1到第n位是没有第二关键字的 所以排名在最前面
        for (int i=1; i<=n; ++i) if (sa[i]>k) y[++num]=sa[i]-k;
        //排名为i的数 在数组中是否在第k位以后
        //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
        //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
        for (int i=1; i<=m; ++i) c[i]=0;
        //初始化c桶
        for (int i=1; i<=n; ++i) ++c[x[i]];
        //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
        for (int i=2; i<=m; ++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个
        for (int i=n; i>=1; --i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        //因为y的顺序是按照第二关键字的顺序来排的
        //第二关键字靠后的,在同一个第一关键字桶中排名越靠后
        //基数排序
        swap(x,y);
        //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思
        x[sa[1]]=1;
        num=1;
        for (int i=2; i<=n; ++i)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
        //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
        if (num==n) break;
        m=num;
        //这里就不用那个122了,因为都有新的编号了
    }
    for (int i=1; i<=n; ++i) putout(sa[i]),putchar(' ');
}
void get_height() {
    int k=0;
    for (int i=1; i<=n; ++i) rk[sa[i]]=i;
    for (int i=1; i<=n; ++i) {
        if (rk[i]==1) continue;//第一名height为0
        if (k) --k;//h[i]>=h[i-1]-1;
        int j=sa[rk[i]-1];
        while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
        height[rk[i]]=k;//h[i]=height[rk[i]];
    }
    putchar(10);
    for (int i=1; i<=n; ++i) putout(height[i]),putchar(' ');
}
int main() {
    cin.getline(s+1,maxn);
    n=strlen(s+1);
    m=122;
    //因为这个题不读入n和m所以要自己设
    //n表示原字符串长度,m表示字符个数,ascll('z')=122
    //我们第一次读入字符直接不用转化,按原来的ascll码来就可以了
    //因为转化数字和大小写字母还得分类讨论,怪麻烦的
    get_SA();
    get_height();
}
//aabaaaab