后缀数组

· · 个人记录

概述

构成

构造

实现原理

int sa[maxn],id[maxn],rk[maxn],oldrk[maxn],cnt[maxn];
/*
sa[i]:排序后排名为 i 的前缀在原数组是 s_? 
rk[i]:原前缀 s_i 在排序后数组的下标或者说排名
sa是不重的,但是rk是重的?? 
cnt:桶 
*/
il void ssa(string &s){//solve suffix array
    ri int i,to=s.size(),m=to>127?to:127,num;//m是rk的上界,受限于第一次要用ascII 
    s=' '+s;
    //处理单字符后缀 
    for(i=1;i<=to;++i) ++cnt[rk[i]=s[i]];//唯一一次的信息获取和朴素计数排序 
    for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];//前缀和。前缀和后,cnt[i]即为被丢到i处的最后一个元素的排名 
    for(i=to;i>=1;--i) sa[cnt[rk[i]]--]=i;//cnt[i]-原cnt[i]+1~cnt[i]恰为其排名

    for(ri int len=1;len<to;len<<=1){//当前处理的是长度为多少的后缀
        memset(cnt,0,sizeof(cnt));//先清空桶。(前缀和意义下的桶用完也不是空的) 
        for(i=1;i<=to;++i) ++cnt[rk[i + len]];
        for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
        for(i=to;i>=1;--i) sa[cnt[rk[i+len]]--]=i;

        memset(cnt,0,sizeof(cnt));
        for(i=1;i<=to;++i) id[i]=sa[i];
        for(i=1;i<=to;++i) ++cnt[rk[id[i]]];
        for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
        for(i=to;i>=1;--i) sa[cnt[rk[id[i]]]--]=id[i];
        //这里用id而上面初始化用i,是要考虑它本身已有的顺序。
        //sa也即id的升序就是 sorted 的序,而rk是做不到这个的
        //第一关键字的排序是基于第二关键字的,从而必须基于上一次的序
        //也即上一次的sa。而rk只负责把它转回原字符串上找拼接
        //如果不基于上一次,那上次的排序结果就被忽略了
        //只有基于上一次的sa的顺序放入和取出,计数排序才是稳定的

        memcpy(oldrk,rk,sizeof(rk));
        for(num=0,i=1;i<=to;++i)
            if(oldrk[sa[i]]==oldrk[sa[i-1]] && oldrk[sa[i]+len]==oldrk[sa[i-1]+len])
                rk[sa[i]]=num;
            else 
                rk[sa[i]]=++num;
    }
}
int sa[maxn],id[maxn],rk[maxn],oldrk[maxn],px[maxn],cnt[maxn];
bool cmp(int x,int y,int len){return oldrk[x]==oldrk[y] && oldrk[x+len]==oldrk[y+len];}
il void ssa(string &s){
    ri int i,to=s.size(),m=to>127?to:127,len,num;//m是rk的上界
    s=' '+s;

    for(i=1;i<=to;++i) ++cnt[rk[i]=s[i]];
    for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
    for(i=to;i>=1;--i) sa[cnt[rk[i]]--]=i;

    for(len=1;;len<<=1,m=num){
        for(num=0,i=to;i>to-len;--i) id[++num]=i;//i+w>n,即第二串为空 
        for(i=1;i<=to;++i) if(sa[i]>len) id[++num]=sa[i]-len;
        //当前串可以做第二串,则将串头按当前串排序放过去 
        memset(cnt,0,sizeof(cnt));
        for(i=1;i<=to;++i) ++cnt[px[i]=rk[id[i]]];
        for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
        for(i=to;i>=1;--i) sa[cnt[px[i]]--]=id[i];

        memcpy(oldrk,rk,sizeof(rk));
        for(num=0,i=1;i<=to;++i)
            rk[sa[i]]=cmp(sa[i],sa[i-1],len)?num:++num;

        if(num==to){
            for(i=1;i<=to;++i) sa[rk[i]]=i;
            break;
        }
    }
}

应用

int hei[maxn];//注意下标是 sorted 的 
il void she(string &s){//solve height
    int to=s.size()-1;
    For(i,1,to){
        hei[rk[i]]=max(0,hei[rk[i-1]]-1);
        while(i+hei[rk[i]]<=to && sa[rk[i]-1]+hei[rk[i]]<=to && s[i+hei[rk[i]]]==s[sa[rk[i]-1]+hei[rk[i]]]) 
            ++hei[rk[i]];
        //去和 sorted[rk[i]-1]做匹配。它对应的是 sa[rk[i]-1] 开始的后缀。 
    }
}