• 后缀数组学习笔记


    前置

    纯属博主虎的的
    罗穗骞2009NOI集训队论文
    还是原版的最明白啊
    先了解基数排序和倍增求sa思想
    并且有一定的看别人博客的基础(对,没错,就是这么不要脸)
    基数排序=>博客
    这里主要说一下代码的理解及重要点

    重点及其目标

    根据后缀子串,他们一定是两两不同的
    rk和sa也是差不多反函数关系,那rk也是不重复的
    所以我们最终的目标是求出rk中无重复时的sa数组,根据定义-最差倍增到n
    总的来说,求sa的过程就是不断地用基数排序倍增直到rk无重复
    而基数排序从代码中很容易看出
    就是是用rk[]和x[]求出sa[]的过程(封装)
    那如何用rk[]和x[]求出sa[]就成了重中之重
    如果明白了这个就基本理解倍增求sa的过程了

    分析目标&&正题

    ps:这里基数排序按照从低到高排序
    我们一共有俩关键字

    FOR(i,n-k+1,n) x[++num]=i;
    FOR(i,1,n) if (sa[i]>k) x[++num]=sa[i]-k;
    

    这是弄出第二关键字排序的x[]的过程
    利用了上次和这次之间的交错关系
    然后有的地方第二关键字没有,也就是-INF,要放在前面,还是老话,画图去
    求出第二关键字排完序的x[]
    这就说明我们已经完成了基数排序的一半任务了
    剩下的就是利用第一关键字来排序了

    FOR(i,1,n) ++c[rk[i]];
    FOR(i,2,m) c[i]+=c[i-1]; 
    ROF(i,n,1) sa[c[rk[i]]--]=i; 
    

    c[rk[x[i]]]-- 下标
    x[i] 权值
    这是根据for循环来臆测的
    x[i]这是按照第二关键字排序的顺序来进行的,我们可以把x[i]看做j
    也就是
    c[rk[j]]-- 下标
    j 权值
    注意到,上下两层的第一关键字是不变的,那第一关键字就好弄了
    我们用上一次的sa捣鼓出来,存在rank里面
    所以就是sa[rk[j]]=j喽,但是,rk是会有重复的呀
    而且我们还不知道c[]的作用呢
    其实c[]就是统计个数来确定的sa的次序的
    这样倍增范围是不断增大,那么重复数也会慢慢变少,直到无重复(定义)

    然后后面就是一堆处理来保证下一次操作的正常运行以及判断结束
    还算好理解,嗯

    代码

    #include <bits/stdc++.h>
    #define FOR(i,a,b) for(int i=a;i<=b;++i)
    #define ROF(i,a,b) for(int i=a;i>=b;--i)
    using namespace std;
    const int maxn=1000050;
    char s[maxn];
    int n,m,x[maxn],rk[maxn],c[maxn],sa[maxn];
    void get_SA()
    {
        FOR(i,1,n) ++c[rk[i]=s[i]];
        //rk[i]是第i个元素的第一关键字 
        FOR(i,2,m) c[i]+=c[i-1]; 
        //做c的前缀和,我们就可以得出每个关键字最多是在第几名 
        ROF(i,n,1) sa[c[rk[i]]--]=i; 
        for(int k=1;k<=n;k<<=1) {
            int num=0;
            //@按照第二关键字进行排序
            FOR(i,n-k+1,n) x[++num]=i;
            FOR(i,1,n) if (sa[i]>k) x[++num]=sa[i]-k;
            FOR(i,1,m) c[i]=0;
            FOR(i,1,n) ++c[rk[i]];
            FOR(i,2,m) c[i]+=c[i-1];
            //@依次取出x[i],按照第一关键字排序
            ROF(i,n,1) sa[c[rk[x[i]]]--]=x[i],x[i]=0;
            //@这里不用想太多,因为要生成新的rt时要用到旧的,就把旧的复制下来,没别的意思 
            swap(x,rk);
            //@制造新的rk
            rk[sa[1]]=1;num=1;
            FOR(i,2,n)
                rk[sa[i]]=(x[sa[i]]==x[sa[i-1]] && x[sa[i]+k]==x[sa[i-1]+k]) ? num : ++num;
            //判断
            if (num==n) break;
            m=num;
        }
        FOR(i,1,n) cout<<sa[i]<<" ";
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1),m=122;
        get_SA();
        return 0;
    }
    
  • 相关阅读:
    第八章 对象
    第七章 压缩列表
    第六章 整数集合
    Java中的Unsafe
    站在Java的角度看LinkedList
    Java内部类详解
    浅析Java中的final关键字
    ConcurrentHashMap
    阻塞队列
    线程池的使用和实现
  • 原文地址:https://www.cnblogs.com/dsrdsr/p/10138452.html
Copyright © 2020-2023  润新知