后缀数组初学

    后缀数组是真的难,看了一整天并没有理解掉,不过能用了大概,能用的意思大概就是:跳过理解求后缀数组的深切内涵而直接套用模板,使用后缀数组来解决问题。没错,后缀数组很难,但是用这些论文上的一句话来形容他的话,解决字符串问题的强有力的工具,工具,的意思就是,的精髓在于使用,而不是用什么工具都要造一个。这算是给自己的菜找借口了啦,其实这样说起来是舍本逐末了,偏离了学算法的初衷,真的尽力了,也就理解了大意,细节上完全混乱,只能等有缘从那个题里灵光乍现了。

    还是要说说对后缀数组的理解:前面学了字典树、ac自动机,实际上就是一个前缀树嘛,那么后缀可不可以建树呢,没道理不可以,所以也有后缀树这种东西,但是后缀树和前缀树差别甚大,在一篇ppt上有些讲解,他的每个节点是一个字符串而不是一个字符(为什么?不知道?反正意思就是很难很复杂),所以我们来学一个简单的后缀数组吧,这可是简单好操作好理解完全不虚后缀树的好东西哦。。。

    后缀树的大意就是把串的后缀排序,没了。说出来我才发现这么简单,没错,好像一个sort(nlogn)能够解决的事情,但是strcmp什么的非线性,不能这么算的。所以选择其他方法倍增算法。求出来sa【】(第几是哪个后缀),rank【】(这个后缀排第几)两个数组。

    倍增算法的意思啊内容啊就是我理解不透的东西了,大意就是。。。。。预处理第一个字母后,如下图:

    涉及到新内容:基数排序。

    然后有了这个后缀数组好像没什么意思啊,所以说,工具,得用啊。然后就加上了LCP(最长公共前缀),及新数组height【】。再然后根据他的性质转化为了RMQ问题,以及RMQ的新模板(ST、标准)。这样就结束了模板之路。

模板代码:

#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];
    int dmin[maxn][20];
    int n;

    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()//min[i, j]表示从第i个数起连续2^j个数中的最大值
    {
        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;



参考资料:PPT 点击打开链接       饶齐(模板) 点击打开链接           RMQ(ST)点击打开链接  点击打开链接

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值