题意
用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;
}