【模板】后缀数组(含代码解析)
说说我的看法吧。
1.sa数组和rnk数组就是一个互逆,原字符串的后缀子串的排名。
2、求sa数组用的是倍增,也就是我一开始不是直接求后缀的,我是求长度为k的子串的排名,在推到k+1的排名的,具体用到基数排序来做。
3.得出来的rnk是从0开始的,注意这一点。然后基数排序时注意有个倒序,具体原因就是旧的sa数组是之前排第二关键字来的,所以得sa数组大的先排,这个yy一下就好了。。
4、然后在求sa的过程中,m(也就是桶的大小是可以优化一下的)。
5.height数组我这里写得是h,h[i]代表着排名为i的后缀子串和排名i-1的后缀子串的最长公共前缀,而又由于这个数组有个性质可以O(n)跑出来。
6、排名相邻的后缀子串相似度最高,也就是公共前缀最长
7、排名i的后缀子串和排名j的后缀子串的最长公共子串等于 h[i+1]到h[j]的最小值,我们可以这样看,i+1的相当于i的串演变过来,(因为相似度最高),然后一步步到j,所以是h的最小值
总复杂度nlogn,因为是倍增log,基数排序O(n)
模板是用我们集训队一位大佬的。不过感觉好像这个板子网上也好常见,,,不过好用就行
下面是无注释的
1 void get_sa(int m){ 2 int n=strlen(s); 3 int *x = t, *y = t2; 4 for(int i = 0; i < m; i++) c[i] = 0; 5 for(int i = 0; i < n; i++) c[x[i] = s[i]]++; 6 for(int i = 1; i < m; i++) c[i] += c[i - 1]; 7 for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i; 8 for(int k = 1; k <= n; k <<= 1) { 9 int p = 0; 10 for(int i = n - k; i < n; i++) y[p++] = i; 11 for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k; 12 for(int i = 0; i < m; i++) c[i] = 0; 13 for(int i = 0; i < n; i++) c[x[y[i]]]++; 14 for(int i = 1; i < m; i++) c[i] += c[i - 1]; 15 for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i]; 16 swap(x, y); 17 p = 1; x[sa[0]] = 0; 18 for(int i = 1; i < n; i++) 19 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++; 20 if(p >= n) break; 21 m = p; 22 } 23 } 24 void get_h(){ 25 int n=strlen(s); 26 int k = 0; 27 for(int i = 0; i < n; i++) rnk[sa[i]] = i; 28 for(int i = 0; i < n; i++) { 29 if(k) k--; 30 int j = sa[rnk[i] - 1]; 31 while(s[i + k] == s[j + k]) k++; 32 h[rnk[i]] = k; 33 } 34 }
下面是加了点注释的
1 void get_sa(int m){ 2 int n=strlen(s); 3 int *x = t, *y = t2; //两个临时数组,x是临时的rnk数组,y是临时的sa数组 4 for(int i = 0; i < m; i++) c[i] = 0; 5 for(int i = 0; i < n; i++) c[x[i] = s[i]]++; //一开始x(rnk)数组初始化为s[i],s是字符串 6 for(int i = 1; i < m; i++) c[i] += c[i - 1]; 7 for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i; 8 for(int k = 1; k <= n; k <<= 1) { 9 int p = 0; 10 //按照i+k的rnk来排sa数组,这里用y暂存sa,也就是谁的i+k的旧rnk小,sa数组就排名靠前 11 for(int i = n - k; i < n; i++) y[p++] = i; //没有后面k位的,自然是最大,这里y是临时的sa 12 for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;//再排有后面k位的。按照后k位的排名作关键字 13 //按照i的rnk来排序 14 for(int i = 0; i < m; i++) c[i] = 0;//清空桶 15 for(int i = 0; i < n; i++) c[x[y[i]]]++; //x是旧的rnk,y是刚刚排完第二关键字的sa数组 16 for(int i = 1; i < m; i++) c[i] += c[i - 1]; 17 for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i]; //更新sa数组,注意要倒序,因为y是排了第二关键字的,所以第二关键字排的后的,现在应该排得后些,也就是先选 18 //然后现在已经更新了sa数组,此时y数组没用了,但是rnk没更新,x是旧的rnk,所以我们把x换给y,用旧的rnk(现在是y了)来更新新的rnk(现在是x) 19 swap(x, y); 20 p = 1; x[sa[0]] = 0;//x是新rnk! 21 for(int i = 1; i < n; i++) 22 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++; 23 if(p >= n) break; //没有一样的rnk就可以退了,因为第一关键字已经没有一样的了,接下来再比第二关键字也没用 24 m = p; //更新桶的大小,因为桶的大小是视rnk最大那个而定 25 } 26 } 27 //这里h数组实际是网上的height数组,也就是下标应该是排名而不是字符串下标 28 void get_h(){ 29 int n=strlen(s); 30 int k = 0; 31 for(int i = 0; i < n; i++) rnk[sa[i]] = i; //求逆 32 for(int i = 0; i < n; i++) { 33 if(k) k--; //因为height数组的性质 34 int j = sa[rnk[i] - 1]; 35 while(s[i + k] == s[j + k]) k++; //至少前k个相同,所以不用比较 36 h[rnk[i]] = k; 37 } 38 }
附上kuangbin的模板吧
这份模板是sa数组是1到n的,然后rnk数组是从0到n-1的,也就是排名是从1开始排名的。
因为感觉之前的模板有锅,因为排名为0的后缀是有意义的,但是询问到他上面就会RE了。这份模板的话先是++n,其余的都一样,那么也就是末尾'\0'会被计算,
放到sa【0】那里,所以就使得sa从1开始是有意义的。这样就不会RE了。kuangbin nb!
1 void get_sa(int m){ 2 int n=strlen(s); 3 ++n; 4 int* x=t,*y=t2; 5 for(int i=0;i<m;++i) c[i]=0; 6 for(int i=0;i<n;++i) c[x[i]=s[i]]++; 7 for(int i=1;i<m;++i) c[i]+=c[i-1]; 8 for(int i=n-1;i>=0;--i) sa[--c[x[i]]]=i; 9 for(int k=1;k<=n;k<<=1){ 10 int p=0; 11 for(int i=n-k;i<n;++i) y[p++]=i; 12 for(int i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k; 13 for(int i=0;i<m;++i) c[i]=0; 14 for(int i=0;i<n;++i) c[x[y[i]]]++; 15 for(int i=1;i<m;++i) c[i]+=c[i-1]; 16 for(int i=n-1;i>=0;--i) sa[--c[x[y[i]]]]=y[i]; 17 swap(x,y); 18 p=1;x[sa[0]]=0; 19 for(int i=1;i<n;++i){ 20 x[sa[i]]=(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k])?p-1:p++; 21 } 22 if(p>=n) break; 23 m=p; 24 } 25 int k=0; 26 n--; 27 for(int i=0;i<=n;++i) rnk[sa[i]]=i; 28 for(int i=0;i<n;++i){ 29 if(k) k--; 30 int j=sa[rnk[i]-1]; 31 while(s[i+k]==s[j+k]) k++; 32 h[rnk[i]]=k; 33 } 34 }