CH1402 后缀数组(Hash)(二分)(排序)

题意

用hash、快排、二分来求后缀数组中的sa和height。

题解

hash+快排+二分
sa数组的朴素求法是用一个string存下来,然后直接对string排序,但这样会爆空间。如果对两个后缀进行逐字的比较复杂度会大大超出。
聪明的出题人想到了用二分+hash的方式来比较字符串的大小。hash的作用还是判断两个子串是否相等,二分的作用是求出第一个不相同的字符,比较这个字符即可得出两个字符串的大小关系。这样把O(|S|)的复杂度降到了O(log|S|)。排序的总复杂度为O(n(logn)^2)。
height数组相比之下就简单多了。根据其定义,就是求两个字符串的最长公共前缀的长度,在搞一次二分+hash即可。

代码

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
const int maxl=300010;
const ull P=131;ull power[maxl];

int sa[maxl],height[maxl];
char s[maxl];int len;
ull h[maxl];

bool check(int l1,int r1,int l2,int r2)
{
    ull tmp1=h[r1]-h[l1-1]*power[r1-(l1-1)];
    ull tmp2=h[r2]-h[l2-1]*power[r2-(l2-1)];
    return tmp1==tmp2;
}

bool cmp(int sa1,int sa2)//[sa1,len] =?= [sa2,len]
{
    int l=0,r=min(len-sa1,len-sa2),ans=-1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(!check(sa1,sa1+mid,sa2,sa2+mid))//[sa1,sa1+mid] =?= [sa2,sa2+mid]
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    if(ans==-1) return sa1>sa2;//如果有字母前缀完全相同,则长的靠后 
    if(s[sa1+ans]<s[sa2+ans]) return 1;
    else return 0;
}

int main()
{
    power[0]=1;for(int i=1;i<maxl;i++) power[i]=power[i-1]*P;
    
    scanf("%s",s+1);
    len=strlen(s+1);
    for(int i=1;i<=len;i++)
    {
        sa[i]=i;
        h[i]=h[i-1]*P+s[i]-'a';
    }
    
    sort(sa+1,sa+len+1,cmp);
    for(int i=1;i<=len;i++) printf("%d ",sa[i]-1);
    printf("\n");
    
    height[1]=0;
    for(int i=2;i<=len;i++)
    {
        int l=1,r=min(len-sa[i-1]+1,len-sa[i]+1),ans=0;
        while(l<=r)
        {
            int mid=l+r>>1;
            if(check(sa[i-1],sa[i-1]+mid-1,sa[i],sa[i]+mid-1))
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        height[i]=ans;
    }
    for(int i=1;i<=len;i++) printf("%d ",height[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/81515663