前言:
紫书代码如果输入的字符串是就会错,需要把;改成,同时的板子也要改一个地方,最后的索引范围为
我太难了,之前看了好久才懂的模板,现在又看不懂了,看了好久第二个模板还是懵逼。。
1.用sort()函数求后缀数组sa[]
总复杂度:O()

#include<bits/stdc++.h>
using namespace std;
const int MAXN=200005;                    //字符串的长度
char s[MAXN];                            //输入字符串
int sa[MAXN],rk[MAXN],tmp[MAXN+1],height[MAXN];
int n,k;

/*比较函数
每一步使得 sa[]从0到n-1对应的字符串(从sa[i]开始的字串)按照组合数大小从小到大排序
最后一步使得sa[]从0到n-1对应的字符串(从sa[i]开始的字串)按照字典序从小到大排列*/
bool comp_sa(int i,int j){                //组合数有两个部分,高位是rk[i],低位是rk[i+k]
    if(rk[i]!=rk[j])                    //先比较高位:rk[i]和rk[j]
        return rk[i]<rk[j];
    else{                                //高位相等,再比较低位的rk[i+k]和rk[j+k]
        int ri = i+k<=n? rk[i+k] : -1;
        int rj = j+k<=n? rk[j+k] : -1;
        return ri<rj;
    }
}
void build_sa(int n){                            //计算字符串s的后缀数组
    for(int i=0;i<=n;i++){
        rk[i]=s[i];                        //字符串的原始数值
        sa[i]=i;                        //后缀数组,在每一步记录当前排序后的结果
    }
    for(k=1;k<=n;k*=2){                    //开始一步步操作,每一步递增两倍进行组合
        sort(sa,sa+n,comp_sa);       //排序,结果记录在sa[]中(根据rk[]值的大小(各个字串的大小)从小到大将rk[]的下标
                                    //(从第几个字符开始的)存在sa[]中)
                                    //r[i]和i是固定的 
        tmp[sa[0]]=0;                    //rk[sa[i]]=i;
        for(int i=0;i<n;i++)            //用sa[]倒推组合数,并记录在tmp[]中,用于临时存放rk[]的新值
            tmp[sa[i+1]]= tmp[sa[i]] + (comp_sa(sa[i],sa[i+1])?1:0);//因为sa[]存的是0~n-1的数,而此时的rk[]有重复 
                                        //的数字,所以如果当前排名i+1的后缀和i的后缀相等就取tmp[sa[i]]
                                        //反之取 tmp[sa[i]]+1 
        for(int i=0;i<n;i++)            //把tmp[]复制给rk[],用于下一步操作
            rk[i] = tmp[i];                //每一步改变最高位 ,最后一步得到每个字串的排名
    }
}
int main(){
    while(scanf("%s",s)!=EOF){
        n=strlen(s);
        build_sa(n+1);                        //求后缀数组
        for(int i=1;i<=n;i++)
            printf("%d ",sa[i]);        //打印后缀数组
        puts("");
    }
    return 0;
}

2.用基数排序求后缀数组sa[]
n个数,每个数有d位,每一位有k种可能
基数排序的复杂度是O(d(n+k)) 存储空间是O(n+k);
当d比较小的情况下,是比快速排序更好的办法;
总复杂度:O(nlog二n)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=200005;                    //字符串的长度
char s[MAXN];                            //输入字符串
int sa[MAXN],cnt[MAXN],t1[MAXN],t2[MAXN],rk[MAXN],height[MAXN];
int n;

void build_sa(int n) {                                //n是字符串长度
    int m=127;                               
    //m是小写字母的ASCII码值范围,构成字符串s的后缀数组,每个字符的值必须为0~m-1
    int i,*x=t1,*y=t2;
    for(i=0;i<m;i++)    cnt[i]=0;
    for(i=0;i<n;i++)    cnt[x[i]=s[i]]++;//记录每个字符出现的次数 
    for(i=1;i<m;i++)    cnt[i]+=cnt[i-1];
    //记录ASCII码小于等于i的字符个数 ,也就是每个字符的排名 
    for(i=n-1;i>=0;i--)  sa[--cnt[x[i]]]=i;
    //--cnt[x[i]]就是一个字符出现多次,最后一个出现的排名最高
    //sa[]:从0到n-1
    for(int k=1;k<=n;k*=2){                    //利用对长度为k的排序结果对长度为2k的排序
        int p=0;
        //2nd
        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;
        //1st
        for(i=0;i<m;i++)    cnt[i]=0;
        for(i=0;i<n;i++)    cnt[x[y[i]]]++;
        for(i=1;i<m;i++)    cnt[i]+=cnt[i-1];
        for(i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]]=y[i];
        swap(x,y);//y为中间数组更新x 
        p=1; x[sa[0]]=0;
        for(i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
            //得到中间的rk[]值 
        if(p>=n)    break;
        m=p;
    }
}
int main(){
    while(scanf("%s",s)!=EOF){
        n=strlen(s);
        build_sa(n+1);                        //求后缀数组
        for(int i=1;i<=n;i++)
            printf("%d ",sa[i]);        //打印后缀数组
        puts("");
    }
    return 0;
}

3.高度数组height[]
和最长公共前缀(LCP)有关

void getheight(){                        //n是字符串长度
    int i,j,k=0;
    for(i=0;i<=n;i++)    rk[sa[i]]=i;    //用sa[]推导rk[]
    for(i=0;i<n;i++){
        if(k)    k--;
        int j=sa[rk[i]-1];
        while(s[i+k]==s[j+k])    k++;
        height[rk[i]]=k;
    }
}