zoukankan      html  css  js  c++  java
  • 后缀数组模版

    要不是因为机房有做得特别快的大佬的话,我估计早就要wate了

    先放一篇大佬的博客

    1180: [视频]后缀数组【模板】后缀排序

    洛谷P3809

    时间限制: 2 Sec  内存限制: 512 MB
    提交: 294  解决: 159
    [提交] [状态] [讨论版] [命题人:admin]

    题目描述

    题目背景
    这是一道模板题。
     

    题目描述
    给出一个字符串,把这个字符串的所有非空后缀从小到大排序后,按顺序输出后缀的第一个字符在原串中的位置。
     

    输入输出格式
    输入格式:
    一个字符串
    输出格式:
    一行,共n个整数,表示答案。
     
    输入输出样例
    输入样例#1:
    ababa
    输出样例#1:

    5 3 1 4 2

     后缀数组真的是一个老难老难的东西了,首先我们要了解一些资料,然后在这里我会放上来供参考啊!

    链接:https://pan.baidu.com/s/1lBsfN6F4u7Q0jbuygw-Q0w
    提取码:omdc (中间的链接请拖到浏览器直接查看)

    然后我们需要了解一个叫基数排序的东西,处理几位数就有几个关键字

    因为是基数排序,所以下面提到的第一关键字和第二关键字都是因为基数排序的使用

    比如说我们要处理一串两位数:

    14 15 23 67 89 

    第一次我们先处理个位的排序,得出来的就是:

    3 4 5 7 9(这个目前作为第一关键字)

    然后我们处理十位的排序,得出来的就是:

    1 1 2 6 8(然后现在这个作为第一关键字)

    3 4 5 7 9(这个作为第二关键字)

    也就是说先处理的会作为第二关键字,后处理的作为第一关键字

    然后我又认真的看了一遍模拟过程,就是说我们先处理个位按照个位处理一个排序,然后再处理十位,按照十位处理一个排序,处理百位也是同样的道理

    这个我不过多解释还是自己看swf解释吧,本质是桶排

    然后接下来我们进入正式的后缀数组的讲解,真的是一个太恶心的东西了,我不会DC3的做法,我只会倍增,目前来说

    • 好的我们先认识两个数组啊
    • 后缀数组:sa[i]就表示排名为i的后缀的起始位置的下标
    • 名次数组:rank[i]就表示起始位置的下标为i的后缀的排名

    简单来说就是,后缀数组就是”排第几的是谁“,名次数组就是”你排第几“

    这两个数组为互逆运算,下面的图一就可以很清楚的看出来(以下图片来自罗穗骞的后缀数组论文)

    然后就要介绍倍增算法

    下面是倍增的实现过程

    大概到这里的话,我们基本思路的实现就已经讲完了,接下来我copy xMinh大佬的倍增实现过程,这个写的实现过程要详细一些

    大佬的博客要详细很多,所以到这里我们就已经把基本上要处理的都处理完了

    然后的话,我们先来看一下这道题的代码实现,然后再讲一个新的东西,毕竟这个新的东西在这道题里面还没有太大的用处

    (注释版,就是一些细节咯,我建议看一下吧,毕竟这个还是挺难的我觉得)

     1 /*https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7%BB%86(maybe)%E8%AE%B2%E8%A7%A3.html*/
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cstdlib>
     5 #include<algorithm>
     6 #include<cmath>
     7 #include<iostream>
     8 using namespace std;
     9 /*
    10 Rank[i]表示编号为i的排名,第i个元素的第一关键字  
    11 sa[i]表示排名为i的编号 
    12 sa数组和Rank数组是相匹配的  
    13 Rsort[i]计数排序的桶 
    14 pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
    15 cnt[i]
    16 排序时:表示当前排序中编号为i的排名
    17 排序后:表示调整Rank前的排名
    18 */
    19 int sa[1100010],Rank[1100010],Rsort[1100010];/*rank是系统的内置数组*/
    20 int a[11000010],cnt[1100010],pos[1100010];
    21 bool cmp(int x,int y,int k){return cnt[x]==cnt[y] && cnt[x+k]==cnt[y+k];}
    22 char s[1100010];
    23 void Suffix(int n,int m) 
    24 {
    25     int k=1,p=0,len;//已经处理好的长度 有多少个不同的后缀 
    26     for(int i=1;i<=n;i++) Rank[i]=a[i];
    27     memset(Rsort,0,sizeof(Rsort));
    28     /*处理k=1*/
    29     for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
    30     for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
    31     for(int i=n;i>=1;i--) sa[Rsort[Rank[i]]--]=i;/*计数排序,设置好Rank和sa数组
    32     基数排序和计数排序是不一样的*/
    33     /*第一关键字和第二关键字都是用于基数排序的,先排的作为第二关键字*/
    34     /*基数排序:类似于桶排序,
    35     先按个位的数字一个个放入0~9的桶里,再按顺序从0~9取出来,(当然如果你是从大到小就是9~0啦!)    
    36     接着再按十位的数字一个个放入桶里,按顺序取出来,如果是两位数,这时我们得到的就是答案!    
    37     以此类推,一直循环到最后一位    
    38     计数排序:就更高级了。这里优化了一下, 就是我们在基数排序所用的桶,是记录他出现的次数,
    39     然后累加(写入代码就是Rsort[i]+=Rsort[i-1]),这样我们得到的Rsort[i]数组里就是不大于i的个数,就是他的排名了!*/
    40     /*基数排序的思想,计数排序的优化*/ 
    41     
    42     for(int k=1;k<n;k<<=1)/*k表示长度,k<<=1等于k*2*/
    43     /*
    44     每次循环
    45     都将两个长度k子串合并为一个长度为2^k的串
    46     其中前k个字符构成的子串的排名为第一关键字,后k个字符为第二关键字
    47     并求出合并后的字符串的排名 
    48     */
    49     /*每次排名得到一共有多少个排名p,由p优化m的值*/
    50     {
    51         len=0;/*先排序第二关键字*/ 
    52         /*
    53         在上一轮中,第一关键字已经排好了, 此时只需要排第二关键字即可。 
    54         y数组是第二关键字排序的结果,存储的是2k长度的字符串的第一关键字的下标 
    55         n-k到n-1中所有的元素第二关键字为0 
    56         */
    57         for(int i=n-k+1;i<=n;i++) pos[++len]=i;
    58         /*
    59         pos[i]表示第二关键字排名为i的数,第一关键字的位置 
    60         第n-k+1到第n位是没有第二关键字的 所以排名在最前面 
    61         */
    62         for(int i=1;i<=n;i++) if(sa[i]>k) pos[++len]=sa[i]-k;
    63         /*
    64         排名为i的数 在数组中是否在第k位以后
    65         如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进pos就行了
    66         所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 
    67         */
    68         for(int i=1;i<=n;i++) cnt[i]=Rank[pos[i]];
    69         memset(Rsort,0,sizeof(Rsort));
    70         for(int i=1;i<=n;i++) Rsort[cnt[i]]++;/*因为上一次循环已经算出了这次的第一关键字,所以直接加就可以了*/
    71         for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];/*第一关键字排名为1~i的数有多少个*/
    72         for(int i=n;i>=1;i--) sa[Rsort[cnt[i]]--]=pos[i];/*更新sa数组*/
    73         /*
    74         因为y的顺序是按照第二关键字的顺序来排的 
    75         第二关键字靠后的,在同一个第一关键字桶中排名越靠后 
    76         基数排序 */
    77         for(int i=1;i<=n;i++) cnt[i]=Rank[i];/*记录之前的排名*/
    78         p=1; Rank[sa[1]]=1;/*初始化*/
    79         for(int i=2;i<=n;i++)
    80         {
    81             if(!cmp(sa[i],sa[i-1],k)) p++;
    82             /*因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字 */
    83             Rank[sa[i]]=p;
    84         }
    85         if(p==n) break; m=p;/*这里就不用122了,因为都有新的编号了*/
    86     }
    87     for(int i=1;i<n;i++) printf("%d ",sa[i]);
    88     printf("%d
    ",sa[n]);
    89 }
    90 int main() 
    91 {
    92     scanf("%s",s+1); int len=strlen(s+1);
    93     for(int i=1;i<=len;i++) a[i]=s[i]-'a'+1;/*方便处理*/
    94     Suffix(len,300);/*'z'是122 ,为了保险设置为130*/
    95     return 0;
    96 }
    Tristan Code 注释版

    (非注释版,可以清晰的看到倍增算法的简洁)

     1 /*https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7%BB%86(maybe)%E8%AE%B2%E8%A7%A3.html*/
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cstdlib>
     5 #include<algorithm>
     6 #include<cmath>
     7 #include<iostream>
     8 using namespace std;
     9 int sa[1100010],Rank[1100010],Rsort[1100010];
    10 int a[11000010],cnt[1100010],pos[1100010];
    11 bool cmp(int x,int y,int k){return cnt[x]==cnt[y] && cnt[x+k]==cnt[y+k];}
    12 char s[1100010];
    13 void Suffix(int n,int m) 
    14 {
    15     int k=1,p=0,len;
    16     for(int i=1;i<=n;i++) Rank[i]=a[i];
    17     memset(Rsort,0,sizeof(Rsort));
    18     for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
    19     for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
    20     for(int i=n;i>=1;i--) sa[Rsort[Rank[i]]--]=i;
    21     for(int k=1;k<n;k<<=1)
    22     {
    23         len=0;
    24         for(int i=n-k+1;i<=n;i++) pos[++len]=i;
    25         for(int i=1;i<=n;i++) if(sa[i]>k) pos[++len]=sa[i]-k;
    26         for(int i=1;i<=n;i++) cnt[i]=Rank[pos[i]];
    27         memset(Rsort,0,sizeof(Rsort));
    28         for(int i=1;i<=n;i++) Rsort[cnt[i]]++;
    29         for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
    30         for(int i=n;i>=1;i--) sa[Rsort[cnt[i]]--]=pos[i];
    31         for(int i=1;i<=n;i++) cnt[i]=Rank[i];
    32         p=1; Rank[sa[1]]=1;
    33         for(int i=2;i<=n;i++)
    34         {
    35             if(!cmp(sa[i],sa[i-1],k)) p++;
    36             Rank[sa[i]]=p;
    37         }
    38         if(p==n) break; m=p;
    39     }
    40     for(int i=1;i<n;i++) printf("%d ",sa[i]);
    41     printf("%d
    ",sa[n]);
    42 }
    43 int main() 
    44 {
    45     scanf("%s",s+1); int len=strlen(s+1);
    46     for(int i=1;i<=len;i++) a[i]=s[i]-'a'+1;
    47     Suffix(len,300);
    48     return 0;
    49 }
    Tristan Code 非注释版

     好的,我刚刚有提到说就是我要讲一个东西,一个新的东西,一个很恶心的证明过程,但是似乎不能在这道题起到半点作用


    但是,既然他在我们后面做题用到了,我就提及一下吧(折腾子串是后缀数组当中常见的)

    先看一个叫做子串的东西,专业术语(来自罗穗骞)

    好的接下来的转自(xMinh大佬的博客)

    其实这个所谓的新东西啊,其实就是我们的后缀数组的一些性质,然后下面的是证明过程

    放一下代码

    下面是(罗穗骞论文的最长公共前缀)

    很快就会发现,我下面发的题都和这个性质有着密切的联系

  • 相关阅读:
    转载:SqlServer数据库性能优化详解
    复杂事件处理技术概览(一)
    Netty : writeAndFlush的线程安全及并发问题
    如何在RCP程序中添加一个banner栏
    AChecker + Selenium2对需要登录的页面进行自动化可访问性测试
    5分钟开启Esper之旅
    使SWT/JFace支持跨平台
    Ubuntu上Docker安装Trouble Shooting
    使Docker Container支持运行SWT程序
    Xcode5中如何切换Storyboards为xib
  • 原文地址:https://www.cnblogs.com/Tristanjiang/p/11376847.html
Copyright © 2011-2022 走看看