重复至少俩次的不重叠子串的个数
若在假设重复子串的长度最多为L的限制下有解, 则对于任意一个比L小的限制L'<L, 也一定有解. 这就说明存在解的连续性
给出一个关于LCP的定理LCP(SA[i], SA[j]) = RMQ(Height[i+1..j]). 由此, 若存在k, 满足Height[k] < L, 则对于所有i, j 满足i < k < j, 有LCP(SA[i], SA[j]) < L. 即公共长度至少为L的两个后缀, 不会跨过一个小于L的Height低谷k, 所以我们可以得到一些由这些低谷划分开的连续的段.
枚举字串长度h
对于每一次的h,利用height数组,找出连续的height大于等于h的里面最左端和最右端得为之l和r。
如果r - l >= h的话,说明没有重叠,答案加1.
原来是这样,我一直不能理解,就是为什么是找出连续的height大于等于h,现在稍微有些体会了,在同一连续height大于等于h的区间中,则公共前缀至少为h,这样也不用担心重复计数了,其他长度为h的重复子串则会出现在另一个连续的大于等于h的height
AC代码:
#include <stdio.h>
#include <string.h>
#define INF 10000000
#define mm 1001
char s[mm];
int sa[mm],rank[mm],height[mm],wa[mm],wb[mm],wv[mm],ws[mm],a[mm];
int max(int a,int b){
return a>b?a:b;
}
int min(int a,int b){
return a<b?a:b;
}
int cmp(int *r,int a,int b,int l) {
return r[a] == r[b] && r[a+l] == r[b+l];
}
void fun(int *r, int n, int m){
int i,j,p,*x = wa, *y = wb, *t;
for(i = 0; i < m; i ++) ws[i] = 0;
for(i = 0; i < n; i ++) ws[x[i] = r[i]] ++;
for(i = 1; i < m; i ++) ws[i] += ws[i-1];
for(i = n-1; i >= 0; i --) sa[--ws[x[i]]] = i;
for(j = 1, p = 1; p < n; j*=2, m = p){
for(p = 0, i = n-j; i < n; i ++) y[p++] = i;
for(i = 0; i < n; i ++)
if(sa[i] >= j)
y[p++] = sa[i]-j;
for(i = 0; i < n; i ++) wv[i] = x[y[i]];
for(i = 0; i < m; i ++) ws[i] = 0;
for(i = 0; i < n; i ++) ws[wv[i]] ++;
for(i = 1; i < m; i ++) ws[i] += ws[i-1];
for(i = n-1; i >= 0; i--) sa[--ws[wv[i]]] = y[i];
for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i ++)
x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;
}
}
void calheight(int *r, int n){
int i, j, k=0;
for(i=1; i<=n; i++)
rank[sa[i]] = i;
for(int i=0; i<n; i++){
if(k) k--;
int j = sa[rank[i]-1];
while(r[i+k] == r[j+k]) k++;
height[rank[i]] = k;
}
}
int getnum(int i,int n){
int ans=0, max1=-1, min1=INF, j;
for(j=2; j<=n; j++){
if(height[j] >= i){
max1 = max(sa[j-1],max(max1,sa[j]));
min1 = min(sa[j-1],min(min1,sa[j]));
}
else{
if(min1!=INF && max1!=-1 && max1-min1>=i)
ans++;
max1=-1;
min1=INF;
}
}
if(min1!=INF&&max1!=-1&&max1-min1>=i)
ans++;
return ans;
}
int main(){
int n;
while(scanf("%s",s) && s[0]!='#'){
n = strlen(s);
for(int i=0; i<n; i++)
a[i] = s[i] - 'a' + 1;
a[n] = 0;
int ans = 0;
fun(a, n+1, 34);//该题字符串由小写组成,最大值小于34
calheight(a, n);
for(int i=1; i<=n/2; i++)
ans += getnum(i,n);
printf("%d\n",ans);
}
return 0;
}