AcWing 140 后缀数组

题目描述:

后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者DC3算法实现,这超出了我们的讨论范围。

在本题中,我们希望使用快排、Hash与二分实现一个简单的O(nlog^2n)的后缀数组求法。

详细地说,给定一个长度为 n 的字符串S(下标 0~n-1),我们可以用整数 k(0≤k<n) 表示字符串S的后缀 S(k~n-1)。

把字符串S的所有后缀按照字典序排列,排名为 i 的后缀记为 SA[i]。额外地,我们考虑排名为 i 的后缀与排名为 i-1 的后缀,把二者的最长公共前缀的长度记为 Height[i]。我们的任务就是求出SA与Height这两个数组。

输入格式

输入一个字符串,其长度不超过30万。

输出格式

第一行为数组SA,相邻两个整数用1个空格隔开。

第二行为数组Height,相邻两个整数用1个空格隔开,我们规定Height[1]=0。

输入样例:

ponoiiipoi

输出样例:

9 4 5 6 2 8 3 1 7 0
0 1 2 1 0 0 2 1 0 2

分析:

本题看起来挺复杂,实际考察的都是前几题考过的字符串Hash + 二分,并没有新的知识点。

对于样例ponoiiipoi,其后缀一共有ponoiiipoi,onoiiipoi,noiiipoi,oiiipoi,iiipoi,iipoi,ipoi,poi,oi,i十个,分别用其首元素所在的下标0 - 9表示,可以发现,对上面的后缀字符串按照字典序排序,则i字典序最小,iiipoi第二小,其下标依次为9和4,其它的类似,所以第一行输出的是排序后的后缀顺序9 4 5 6 2 8 3 1 7 0。对于i,没有比它更小的后缀了,所以其与前面元素公共前缀长度为0,对于iiipoi,与其前面元素i公共前缀长度为1,所以第二行依次输出0 1 2 1 0 0 2 1 0 2,以此来表示按字典序排序后相邻两个元素公共前缀的长度。

首先考虑如何对这么多后缀字符串进行排序,我们知道,一般的排序是需要O(nlogn)时间的,而对于两个字符串,比较下大小的时间复杂度就为O(n),对字符串数组排序的话总的时间复杂度就为O(n^2 * logn)了。考虑用字符串Hash + 二分来将两个字符串比较大小的过程优化到O(logn)。例如abcfe和abcde,对其分别做了hash映射后,l = 0,r =,4,mid = 2,通过字符串hash值发现abc = abc,于是l = 2,mid = 3,发现abcf != abcd,于是r = mid - 1 = 2,此时l = r,循环终止,找到了最长公共前缀的长度为3。于是我们可以直接比较下标为3的字符f和d,得到abcfe > abcde。倘若一个字符串为另一个字符串的前缀呢?比如比较abc和abcd时,公共长度为3,abcd的下标为3的字符是d,而abc的下标为3的字符就是空的了,为了处理这种情况,就直接将abc的下标为3的元素设为无穷小,就可以比较出二者的大小了。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <climits>
using namespace std;
typedef unsigned long long ull;
const int maxn = 300010,base = 131;
int n;
char str[maxn];
ull h[maxn],p[maxn];
int sa[maxn],height[maxn];
int get(int l,int r){
    return h[r] - h[l - 1] * p[r - l + 1];
}
int max_common_prefix(int a,int b){
    int l = 0,r = min(n - a + 1,n - b + 1);
    while(l < r){
        int mid = l + r + 1 >> 1;
        if(get(a,a + mid - 1) != get(b,b + mid - 1))    r = mid - 1;
        else    l = mid;
    }
    return l;
}
bool cmp(int a,int b){
    int l = max_common_prefix(a,b);
    int av = a + l > n ? INT_MIN : str[a + l];
    int bv = b + l > n ? INT_MIN : str[b + l];
    return av < bv;
}
int main(){
    scanf("%s",str + 1);
    n = strlen(str + 1);
    p[0] = 1;
    for(int i = 1;i <= n;i++){
        h[i] = h[i - 1] * base + str[i] - '0';
        p[i] = p[i - 1] * base;
        sa[i] = i;
    }
    sort(sa + 1,sa + 1 + n,cmp);
    for(int i = 1;i <= n;i++)   printf("%d ",sa[i] - 1);
    puts("");
    for(int i = 1;i <= n;i++){
        if(i == 1)  printf("0 ");
        else{
            printf("%d ",max_common_prefix(sa[i - 1],sa[i]));
        }
    }
    puts("");
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值