题目链接:https://www.luogu.com.cn/problem/P3809
题意:给定长为n的字符串s,将s的每个后缀字符串排序(升序),从小到大输出后缀字符串在s中出现的第一个位置。
思路:
今天碰到一个要用后缀数组的题,于是来洛谷刷模板题,搞了一整天终于明白了板子怎么写。
参考大佬的博客:https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7%BB%86(maybe)%E8%AE%B2%E8%A7%A3.html
求sa数组的复杂度为O(nlogn)(博客中提到求sa数组还有一种O(n)的D3C算法,但比如倍增容易理解,代码较难实现,所以暂时只学倍增法),求height数组的复杂度为O(n)。
AC code(含注释):
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1000005; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m; //倍增+基数排序求sa,O(nlogn) void get_SA(){ for(int i=1;i<=n;++i){ x[i]=s[i]; ++c[x[i]]; } //c数组是桶,x[i]是第i个元素的第一关键字 for(int i=2;i<=m;++i) c[i]+=c[i-1]; //求c的前缀和,得到每个关键字最多在第几名 for(int i=n;i>=1;--i) sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n+1-k;i<=n;++i) y[++num]=i; //y[i]表示第二关键字排名为i的数,第一关键字的位置 //第n-k+1到第n位没有第二关键字,排在最前面 for(int i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k; //排名为i的数,在树组中是否在第k位以后 //如果满足,那么它可以作为别人的第二关键字 //就把它的第一关键字的位置添加进y就行了 //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 for(int i=1;i<=m;++i) c[i]=0; //初始化桶c for(int i=1;i<=n;++i) ++c[x[i]]; //因为上一次循环已经算出这次的第一关键字了,所以直接加就行 for(int i=2;i<=m;++i) c[i]+=c[i-1]; //求c的前缀和,得到每个关键字最多在多少名 for(int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0; //因为y的顺序是按照第二关键字的顺序来排的 //第二关键字靠后的,在同一第一关键字桶中排名越靠后 //基数排序 swap(x,y); x[sa[1]]=1,num=1; for(int i=2;i<=n;++i) x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])? num:++num; //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次循环的第一关键字 if(num==n) break; //如果所有的序号都不一样,那么排序完毕 m=num; //这里不用原来的122了,因为有新的编号了 } for(int i=1;i<=n;++i){ if(i!=1) printf(" "); printf("%d",sa[i]); } } //O(n) //height[i]=LCP(i,i-1) //LCP为suff(sa[i])与suff(sa[j])的最长公共前缀 //即排在第i的字符串和排在第j的字符串的最长公共前缀 //hi[i]=height[rk[i]] //即第i个字符串和排序后在它前面的字符串的最长公共前缀 void get_height(){ int k=0; for(int i=1;i<=n;++i) rk[sa[i]]=i; for(int i=1;i<=n;++i){ if(rk[i]==1) continue; //height[1]=0 if(k) --k; //因为h[i]>=h[i-1]-1 int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k; height[rk[i]]=k; //h[i]=height[rk[i]]; } } int main(){ scanf("%s",s+1); n=strlen(s+1); m=122; //ascii('z')=122,表示字符个数 //第一次读入字符直接按照ascii码来 get_SA(); //get_height(); return 0; }