后缀数组的运用主要体现在三个数组的使用之中
1.sa[i]=n;
表示的是排名是第i名的是第n个后缀
2.rank[n]=i;
表示的是第n个后缀在排序中是排在第几位的,它和sa数组是相反的。
3.height[ ]
表示的是相邻的两个后缀之间的最长的公共前缀。
这三种数组的运用是有很多种,先总结两种。
一,求出现至少k次的最长不重复子串
这就是height[ ]最典型的运用。一般的思路是二分总的字符串的长度,来寻找符合条件的height[ ]中的段落,一旦出现符合条件的段落就记录下来,同时输出就可以。
例题 UVA - 11107
题意:给出n个字符串,要求你找出至少在(n)/2个字符串中出现过的最长字串。
思路;这是一道典型的使用height[ ]数组的题目,这里只需要将给出的字符串都合并起来,同时在每一个合并的地方加上一个从来都没有出现过的字符,避免在两个字符串相交的地方出现符合条件的字串。同时在最后合并的最后加上一个没有出现过的最小的字符。这是为了,在建立三个数组的时候更好操作。所以这题我们只要对一个长度进行二分,然后在height[ ]数组中分段就好了。那么我们选择什么长度呢,这里只要选择所有给出的串中最长的一个,显然这是很符合条件的。那么下面就是代码
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> #include<vector> #include<stack> #include<bitset> #include<cstdlib> #include<cmath> #include<set> #include<list> #include<deque> #include<map> #include<queue> using namespace std; typedef long long ll; const double PI = acos(-1.0); const double eps = 1e-6; const int mod =1000000; const int maxn =140110; char s[1100]; int str[maxn]; int Rank[maxn],height[maxn]; int sa[maxn],t[maxn],t2[maxn],c[maxn]; int belong[maxn],visit[200]; void build_sa(int * s,int n,int m) { int i,*x = t,*y = t2; for(i = 0;i < m;i++)c[i] = 0; for(i = 0;i < n;i++)c[ x[i] = s[i] ]++; for(i = 1;i < m;i++)c[i] += c[i-1]; for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i; for(int k = 1;k <= n;k <<= 1) { int p = 0; for(i = n - k;i < n;i++) y[p++] = i; for(i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k; for(i = 0;i < m;i++) c[i] = 0; for(i = 0;i < n;i++) c[ x[y[i]] ]++; for(i = 0;i < m;i++) c[i] += c[i-1]; for(i = n-1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i]; swap(x,y); p = 1; x[sa[0]] = 0; for(i = 1;i < n;i++) x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++; if(p >= n) break; m = p; } } void calheight(int * s,int n) { int i,j,k = 0; for(i = 0;i < n;i++)Rank[sa[i]] = i; for(i = 0;i < n;i++) { if(k) k--; int j = sa[Rank[i]-1]; while(s[i+k] == s[j+k]) k++; height[Rank[i]] = k; } } int Judge(int n,int len,int num) { int i,j,k; int cnt=0; memset(visit,0,sizeof(visit)); visit[0]=1; if(!visit[belong[sa[0]]]) { cnt++; visit[belong[sa[0]]]=1; } for(i=1;i<n;i++) { if(height[i]<len) { cnt=0; memset(visit,0,sizeof(visit)); visit[0]=1; if(!visit[belong[sa[i]]]) { cnt++; visit[belong[sa[i]]]=1; } } else { if(!visit[belong[sa[i]]]) { cnt++; visit[belong[sa[i]]]=1; } } if(cnt>=num) return 1; } return 0; } void out(int n,int len,int num) { int i,j,cnt=0; memset(visit,0,sizeof(visit)); visit[0]=1; if(!visit[belong[sa[0]]]) { cnt++; } visit[belong[sa[0]]]=1; for(i=1;i<n;i++) { if(height[i]<len) { if(cnt>=num) { for(j=sa[i-1];j<sa[i-1]+len;j++) printf("%c",str[j]-20); printf(" "); } cnt=0; memset(visit,0,sizeof(visit)); visit[0]=1; if(!visit[belong[sa[i]]]) { cnt++; visit[belong[sa[i]]]=1; } } else { if(!visit[belong[sa[i]]]) { cnt++; visit[belong[sa[i]]]=1; } } } if(cnt>=num) { for(j=sa[n-1];j<sa[n-1]+len;j++) printf("%c",str[j]-20); printf(" "); } } int main() { int n; int flag=1; while(scanf("%d",&n)==1&&n) { if(!flag) printf(" "); else flag=0; int i,j,k; int pos=0,cnt=1,left=0,right=0; memset(belong,0,sizeof(belong)); for(i=1;i<=n;i++) { scanf("%s",s); int num=strlen(s); right=max(right,num); for(j=0;j<num;j++) { str[pos+j]=int(s[j])+20; belong[pos+j]=i; } str[pos+num]=cnt++; pos=pos+num+1; } str[pos]=0; build_sa(str,pos+1,150); calheight(str,pos+1); int max_x=0; while(left<=right) { int mid=(left+right)>>1; if(Judge(pos+1,mid,n/2+1)) { max_x=mid; left=mid+1; } else { right=mid-1; } } //cout<<max_x<<"....."<<endl; if(max_x==0) { printf("? "); } else { out(pos+1,max_x,n/2+1); } } return 0; }
2.求出现k次或者是至少k次的最长可重叠子串
这个问题和上面的问题就很类似,实际上差别就不会很大,上面的要求是不能重叠,而这里则是可以重叠的,那么反而这个类型是更加简单的。同样的方法解答,但是不需要记录每一个对height[ ]遍历的时候辨别当前的子串是不是属于同一个已经辨别过的子串。
题意:给你一个n和一个字符串,要求你求出在这个字符串中出现了n次的最长字串的长度以及最后一个出现的位置。
思路;这一题时间上十分的简单,只要二分height[ ]数组,同时注意输出就好了。
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> #include<vector> #include<stack> #include<bitset> #include<cstdlib> #include<cmath> #include<set> #include<list> #include<deque> #include<map> #include<queue> using namespace std; typedef long long ll; const double PI = acos(-1.0); const double eps = 1e-6; const int mod =1000000; const int maxn =140110; char s[50000]; int str[maxn]; int Rank[maxn],height[maxn]; int sa[maxn],t[maxn],t2[maxn],c[maxn]; int max_i; int max_x=0; void build_sa(int * s,int n,int m) { int i,*x = t,*y = t2; for(i = 0;i < m;i++)c[i] = 0; for(i = 0;i < n;i++)c[ x[i] = s[i] ]++; for(i = 1;i < m;i++)c[i] += c[i-1]; for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i; for(int k = 1;k <= n;k <<= 1) { int p = 0; for(i = n - k;i < n;i++) y[p++] = i; for(i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k; for(i = 0;i < m;i++) c[i] = 0; for(i = 0;i < n;i++) c[ x[y[i]] ]++; for(i = 0;i < m;i++) c[i] += c[i-1]; for(i = n-1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i]; swap(x,y); p = 1; x[sa[0]] = 0; for(i = 1;i < n;i++) x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++; if(p >= n) break; m = p; } } void calheight(int * s,int n) { int i,j,k = 0; for(i = 0;i < n;i++)Rank[sa[i]] = i; for(i = 0;i < n;i++) { if(k) k--; int j = sa[Rank[i]-1]; while(s[i+k] == s[j+k]) k++; height[Rank[i]] = k; } } int Judge(int n,int len,int num) { int cnt=1; int i,j; for(i=1;i<n;i++) { if(height[i]<len) { cnt=1; } else { cnt++; } if(cnt>=num) { return 1; } } return 0; } void out(int n,int len,int num) { int cnt=1; int i,j; for(i=1;i<n;i++) { if(height[i]<len) { cnt=1; } else { cnt++; } if(cnt>=num) { int num2=cnt; for(j=i-cnt+1;j<=i;j++) { if(sa[j]>=max_i) { max_i=sa[j]; } } } } printf("%d %d ",max_x,max_i); } int main() { int n; while(scanf("%d",&n)==1&&n) { int i,j,k; scanf("%s",s); int num=strlen(s); for(i=0;i<num;i++) { str[i]=int(s[i]); } str[num]=0; build_sa(str,num+1,200); calheight(str,num+1); int l=0,r=num; max_x=0; max_i=0; while(l<=r) { int mid=(l+r)>>1; if(Judge(num+1,mid,n)) { max_x=mid; l=mid+1; } else { r=mid-1; } } if(n==1) { printf("%d 0 ",num); } else{ if(max_x==0) { printf("none "); } else { out(num+1,max_x,n); //cout<<max_x<<endl; } } } return 0; }
待续....