zoukankan      html  css  js  c++  java
  • 基数排序和后缀数组的原理入门

    字符串里算难了。。参考了好多博客

    推荐https://blog.csdn.net/YxuanwKeith/article/details/50636898

    每个数组的含义:sa[i]:排在第i位的子串的位置,、

            rank[i]:位置 i 的子串排在第几位

            height[i]:位置为sa[i]的后缀和位置为sa[i-1]的后缀的最长公共前缀,即排名相邻的两个后缀的最长公共前缀

            height[rank[i]]:相邻两个后缀的最长公共前缀

            tax[i]:桶

            tp[i]:基数排序辅助数组,意义类似于sa[i],即按第二关键字顺序排在第i位的子串的位置

    rsort()函数:基数排序:用更新sa数组

    void rsort(){//桶排序,每个桶是第一关键字,桶内次序是第二关键字
        for(int i=0;i<=m;i++)tax[i]=0;//桶清空
        for(int i=1;i<=n;i++)tax[rank[tp[i]]]++;//把子串按照第二关键字次序放进桶中,越先放进桶内的第二关键字越小
        for(int i=1;i<=m;i++)tax[i]+=tax[i-1];//求桶的前缀和
        for(int i=n;i>=1;i--)sa[tax[rank[tp[i]]]--]=tp[i];//按第二关键字倒序从桶中抽出子串,tp[i]即是对应子串的位置
    }        

    suffix()函数:求出sa数组,rank数组,并最终求出height数组

    void suffix(){                
        for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i;
        int p; m=127; rsort();
                            
        for(int w=1;p<n;w<<=1,m=p){//桶的个数更新
            for(int i=n-w+1,p=0;i<=n;i++) tp[++p]=i;//长度越界的串第二关键字是0(即排在最前面)
            for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;//未越界则用上一层的sa求出tp数组
    
            rsort();swap(rank,tp);rank[sa[1]]=1;p=1;//更新sa数组,将旧rank转给tp
            for(int i=2;i<=n;i++) rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;//离散化rank(把相等的字符串的rank设置为相同),子串相等的用同一个序号
        }        
                
        //求出height数组
        int j,k=0;
        for(int i=1;i<=n;height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k)
                ;
    }            

    如何求出height数组

    利用性质height[rank[i]] >= height[rank[i-1]]-1

    证明:设suffix[k]是suffix[i-1]前一名的后缀,则其其最长公共前缀是height[rank[i-1]],都去掉第一个字符,就变成suffix[k+1]和suffix[i],

       显然suffix[k+1]排在suffix[i]前面,并且其最长公共前缀为height[rank[i-1]]-1(就是去掉了第一个字符,其他是一样的)

          那么height[rank[i]] 要么 就是 suffix[k+1], 要么比suffix[k+1]排名更靠近suffix[i],所以height[rank[i]] >= height[rank[i-1]]-1

    不用直接求height,而是通过height[rank[i]]间接求

    //求出height数组
        int j,k=0;
        for(int i=1;i<=n;height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k)//k=height[rank[i-1]]-1,j是证明中的k,
                ;

    cmp()函数:比较两个二元串是否完全相等

    int cmp(int *f,int x,int y,int w){return f[x]==f[y] && f[x+w]==f[y+w];}

    下面是例题:求字符串中出现两次及以上的子串

    /*
    后缀数组:suffix[i]:字符串第i-len的子串,即i开始的后缀
             sa[i]:按字典序排在第i个的是第sa[i]个后缀
             rank[i]:第i个后缀按字典序排在rank【i]位
             height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,即排在第i个后缀和第i-1个后缀的公共前缀
    性质1:rank[j]<rank[k]==>后缀j...k的最长公共前缀是min(height[rank[j]+1],height[rank[j]+2]...height[rank[k]])
        假设后缀j的排名小于k,那么这些排名连续的后缀的最长前缀就是上面公式
    
    性质2:height[rank[i]]>=height[rank[i-1]]-1
    证明:设suffix[k]是排在suffix[i-1]前一名的后缀,则按照height数组定义,其最长公共前缀是height[rank[i-1]]
        显然suffix[k+1]排在suffix[i]前面,并且其最长公共前缀为height[rank[i-1]]-1     
        所以height[rank[i]]即suffix[i]和其前一名的最长公共前缀,必定不小于height[rank[i-1]]-1
    
    可以按照height[rank[1]],height[rank[2]]...计算顺序
    所有后缀子串的前缀子串即为原串的所有子串
    
    怎么构造rank和sa数组?基数排序即可
    怎么构造height数组?利用性质2即可
    */
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorihtm>
    using namespace std;
    
    #define maxn 100005
    
    char ch[maxn],all[maxn];
    int sa[maxn],rank[maxn],height[maxn],tax[maxn],tp[maxn],a[maxn],n,m;
    //rank[i]:第i个后缀的排名,sa[i]排名i的后缀的位置;
    //height[i]排名为i的后缀与排名为i-1的后缀的最长公共前缀
    //tax[i]基数排序辅助数组(就是桶),tp[i],rank的辅助数组(第二关键字),类似于sa数组
    //为什么要增加一个tp数组,因为前几次排序后可能有好多个串的排序号是一样的,这时sa[i]就无法精确表示第i个串是第几个串
    //通过增加tp数组,tp[i]表示排名i的串是第几个串,rank[tp[i]]表示i在双关键字排序下的序号(rank[tp[i]]==rank[tp[j]]的情况)
    //a 原串
    char str[maxn];
    void rsort(){//基数排序
        for(int i=0;i<=m;i++) tax[i]=0;//每个桶清空
        for(int i=1;i<=n;i++) tax[rank[tp[i]]]++;//桶上累计
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];//桶累加求前缀和,以此求出所有每个桶对应的序号
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];//找到那个桶的值,对应这个排名的就是第tp[i]个数,之后把桶里的数减1即可!
    }
    
    int cmp(int *f,int x,int y,int w){return f[x]==f[y] && f[x+w]==f[y+w];}//比较两个关键字
    
    void suffix(){
        for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i;
        m=127,rsort();//一开始以单个字符为单位,所以m=127
        
        for(int w=1,p=1;p<n;w<<=1,m=p){//倍增法,不断更新rank
            //w:当前子串长度,m:当前离散后排名种类数
            //tp:第二关键字,可以由上一次sa得到
    
            p=0;
            for(int i=n-w+1;i<=n;i++) tp[++p]=i;//长度越界,第二关键字为0,退化为下标即可
            for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;//排名为p的第二关键字是属于串中下标为a[i]-w的;
            //字符串首w位不会变成第二关键字
    
            //要使rank[sa[i]]=i,更新sa,用tp暂存上一轮的rank,离散rank
            rsort(),swap(rank,tp),rank[sa[1]]=p=1;
    
            //用已经完成的sa来更新rank,并离散化:把相等字符串的rank设置为相同
            for(int i=2;i<=n;i++) rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p,++p;
        }
        //求出height数组
        int j,k=0;
        for(int i=1;i<=n;height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k)//k=height[rank[i-1]]-1,j是证明中的k,
                ;
            
    }   
            
            
    void init(){        
        scanf("%s",str);    
        n=strlen(str);      
        for(int i=0;i<n;i++)
            a[i+1]=str[i];    
    }                       
    int main(){            
        init();             
        suffix();           
        int ans=height[2];  
    
        for(int i=3;i<=n;i++) 
            ans+=max(height[i]-height[i-1],0);
        printf("%d
    ",ans);
    }
    //询问一个字符串中有多少至少出现两次的子串,
    //height[i]是两个后缀的前缀长度,为答案做的贡献是以前缀首字母开始的长度从1到height[i]个子串
    //同时该贡献要减去height[i-1]的贡献,即如果首字母相同的情况下造成的重复计算
    //但是如果这个差小于0,那么显然第i个后缀和第i-1个后缀首字母就不相同,那么这种情况贡献是0

    关于height数组的一些应用:height[i]是排名i和排名i-1的两个后缀的最长公共前缀

    因为height数组是两个排名相邻的串的前缀,那么全串的最长公共前缀必是max{height[i]}

    1.求一个串中两个串的最大公共前缀:

      就是height数组,用rmq预处理,再O(1)查询,

    2.一个串中可重叠的重复最长子串:即求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是height

  • 相关阅读:
    #256 (Div. 2)A. Rewards
    1113 矩阵快速幂
    1108 距离之和最小V2
    1287 加农炮
    1191 消灭兔子
    1051 最大子矩阵
    1086 背包
    1105 第K大的数
    2016 CCPC 网络赛 B 高斯消元 C 树形dp(待补) G 状压dp+容斥(待补) H 计算几何
    Educational Codeforces Round 18 C dp,思维 D lowbit,思维
  • 原文地址:https://www.cnblogs.com/zsben991126/p/10134196.html
Copyright © 2011-2022 走看看