AcWing 140 后缀数组

    xiaoxiao2023-11-11  151

    题目描述:

    后缀数组 (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)