CH 1402 - 后缀数组 - [字符串hash]

题目链接:传送门

描述

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

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

详细地说,给定一个长度为 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

样例解释

排名第一(最小)的后缀是9(S[9~9],即字符串 i),第二的是后缀4(S[4~9],即字符串iiipoi),第三的是后缀5(S[5~9],即字符串iipoi)以此类推。Height[2]表示排名第2与第1的后缀的最长公共前缀,长度为1,Height[3]表示排名第3与第2的后缀的最长公共前缀,长度为2,以此类推。

题解:

假设字符串长度为 $N$,那么如果用暴力的方式来比较两个后缀子串的字典序大小(以及求最长公共前缀的长度),显然一次比较是 $O\left( {N} \right)$ 的时间复杂度,

如果用快排对 $N$ 个后缀子串进行排序,那么就要 $O(N^2 \log N)$ 的时间复杂度,

我们尝试考虑优化的地方:比较两个字符串的时间,从 $O(N)$ 降到 $O(\log N)$,

对于两个后缀子串,或者更一般的,对于两个字符串,怎么更快速的比较字典序大小,或者,怎么更快速求的最长公共前缀?

容易想到,可以二分最长公共前缀的长度,用字符串哈希 $O(1)$ 判断是否两个前缀子串是否一样,

然后判断两个字符串中哪个字典序更大就很简单了,去掉最长公共前缀,比较一下剩下的第一个字符即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;

const int P=131;
const int maxn=300000+10;

char s[maxn];
int len;
int sa[maxn],h[maxn];

ull pre[maxn],Ppow[maxn];
void pretreat()
{
    pre[0]=0;
    Ppow[0]=1;
    for(int i=1;i<=len;i++)
    {
        pre[i]=pre[i-1]*P+(s[i]-'a'+1);
        Ppow[i]=Ppow[i-1]*P;
    }
}

inline bool isSame(int l1,int r1,int l2,int r2)
{
    return pre[r1]-pre[l1-1]*Ppow[r1-(l1-1)] == pre[r2]-pre[l2-1]*Ppow[r2-(l2-1)];
}
inline int maxpre(int a,int b)
{
    int l=0,r=min(len-a+1,len-b+1),mid;
    while(l<r)
    {
        mid=(l+r)/2+1;
        if(isSame(a,a+mid-1,b,b+mid-1)) l=mid;
        else r=mid-1;
    }
    return l;
}
bool cmp(int a,int b)
{
    int mp=maxpre(a,b);
    return s[a+mp]<=s[b+mp];
}

int main()
{
    scanf("%s",s+1);
    len=strlen(s+1);
    pretreat();
    for(int i=1;i<=len;i++) sa[i]=i;
    sort(sa+1,sa+len+1,cmp);
    for(int i=1;i<=len;i++)
    {
        if(i==1) h[i]=0;
        else h[i]=maxpre(sa[i-1],sa[i]);
        printf("%d%c",sa[i]-1,i<len?' ':'\n');
    }
    for(int i=1;i<=len;i++) printf("%d%c",h[i],i<len?' ':'\n');
}

1A开心

时间复杂度:

排序 $O(N \log^2 N)$,计算Height数组 $O(N \log N)$,总时间复杂度 $O(N \log^2 N)$。

猜你喜欢

转载自www.cnblogs.com/dilthey/p/9695162.html
今日推荐