字符串--后缀数组的运用

时间:2023-01-07 11:04:08

后缀数组的运用主要体现在三个数组的使用之中

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("\n");
}
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("\n");
}
}
int main()
{
int n;
int flag=1;
while(scanf("%d",&n)==1&&n)
{
if(!flag) printf("\n");
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("?\n");
}
else
{
out(pos+1,max_x,n/2+1);
}
}
return 0;
}
2.求出现k次或者是至少k次的最长可重叠子串
这个问题和上面的问题就很类似,实际上差别就不会很大,上面的要求是不能重叠,而这里则是可以重叠的,那么反而这个类型是更加简单的。同样的方法解答,但是不需要记录每一个对height[ ]遍历的时候辨别当前的子串是不是属于同一个已经辨别过的子串。
例子  UVALive - 4513
题意:给你一个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\n",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\n",num);
}
else{
if(max_x==0)
{
printf("none\n");
}
else
{
out(num+1,max_x,n);
//cout<<max_x<<endl;
}
}
}
return 0;
}

  待续....