后缀数组 S A [ ] SA[] SA[]保存的是 1 ∼ n 1\sim n 1∼n的一个排列,其每个位置的元素代表将整个字符串的 n n n个后缀排序后第 i i i小的后缀的首字母的下标。
在求之前,先记住几个变量所代表的含义:
上面两个数组可以理解为是“逆运算”的关系,所以有 s a [ r k [ i ] ] = r k [ s a [ i ] ] = i sa[rk[i]]=rk[sa[i]]=i sa[rk[i]]=rk[sa[i]]=i。
现在问题回到如何求上。
对于 s a [ ] sa[] sa[],考虑倍增,可以利用相邻的两个 2 i 2^i 2i长度的串拼起来来实现 2 i + 1 2^{i+1} 2i+1长度的串的大小比较。
因为有大量的重合部分,采用基数排序。
int n=strlen(s+1),m=10000,p=0;
for (int i=1;i<=n;i++) wb[x[i]=s[i]]++;
for (int i=1;i<=m;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[i]]--]=i;
for (int j=1;p<n;j*=2,m=p)
{
p=0;
for (int i=n-j+1;i<=n;i++) y[++p]=i;
for (int i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (int i=1;i<=m;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=m;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
for (int i=1;i<=n;i++) swap(x[i],y[i]);
p=1,x[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p;
else x[sa[i]]=++p;
}
}
解释其中几点:
求得 s a [ ] sa[] sa[]后, r k [ s a [ i ] ] = i rk[sa[i]]=i rk[sa[i]]=i求得 r k [ ] rk[] rk[]。
for (int i=1;i<=n;i++)
rk[sa[i]]=i;
考虑如何求 h e i g h t [ ] height[] height[]。
h e i g h t [ ] height[] height[]数组有一个性质: h e i g h t [ r k [ i ] ] ≥ h e i g h t [ r k [ i − 1 ] ] − 1 height[rk[i]]\ge height[rk[i-1]]-1 height[rk[i]]≥height[rk[i−1]]−1
证明略。。。
所以我们按照排名来计算,即可做到 O ( n ) O(n) O(n)得到 h e i g h t [ ] height[] height[]。
int k=0;
for (int i=1;i<=n;i++)
{
if (k) k--;
if (rk[i]==1) continue;
int j=sa[rk[i]-1];
while (s[j+k]==s[i+k]) k++;
height[rk[i]]=k;
}
如果我们要求两个后缀的最长公共前缀,有如下方法。
性质: s u f f i x ( i ) suffix(i) suffix(i)和 s u f f i x ( j ) suffix(j) suffix(j)的最长公共前缀为 min { h e i g h t [ r k [ i ] + 1 ] , h e i g h t [ r k [ i ] + 2 ] , ⋯   , h e i g h t [ r k [ j ] ] } \min\{height[rk[i]+1],height[rk[i]+2],\cdots,height[rk[j]]\} min{height[rk[i]+1],height[rk[i]+2],⋯,height[rk[j]]}
所以我们根据求出的 h e i g h t height height数组,构造出ST表或线段树即可高效的查询。
给定一个字符串,求至少在这个串中出现超过两次的最长子串长度(可重叠)。
思路:考虑这个出现超过两次的串,他一定是至少两个后缀的前缀,所以我们只需要求所有后缀的LCP的最大值即可。而我们的后缀数组是已经按照字典序排好的,相隔越近的,他的LCP才有可能更长,故所有最长的LCP,就是我们的 h e i g h t [ ] height[] height[]数组。所以只需要对 h e i g h t [ ] height[] height[]取max即可。
上题的不可重叠版本。
思路:考虑二分这个串的长度 m i d mid mid。
然后我们将 h e i g h t [ ] height[] height[]数组分组,分界线为 h e i g h t [ ] < m i d height[]<mid height[]<mid,单独考虑每一组,他们之间任意两个串都满足 L C P > m i d LCP>mid LCP>mid,那么我们只需要找到这一组中 s a [ ] sa[] sa[]的最大值和最小值,看是否相差超过 m i d mid mid即可。
题目链接
给定两个字符串,求这两个串的最长公共子串。
子串肯定是一个后缀的前缀。
所以将两个字符串拼起来,中间加一个没有出现的字符'\0'
,然后求出后缀数组,求出 h e i g h t [ ] height[] height[]的最大值即可。
唔。。。好像有点毛病?如果这个 s a sa sa中相邻的两个串属于同一个原串就不合法了欸。
考虑记录分界线在哪里,对于所有 ( s a [ i ] > p o s ) ≠ ( s a [ i + 1 ] > p o s ) (sa[i]>pos)\ne(sa[i+1]>pos) (sa[i]>pos)̸=(sa[i+1]>pos)即两个端点分居两侧的后缀才统计答案即可。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define pa pair
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
const int MAXN=20010;
int wb[MAXN],x[MAXN],y[MAXN];
int rk[MAXN],sa[MAXN],height[MAXN];
char s[MAXN];
void build_sa()
{
int n=strlen(s+1),m=10000,p=0;
memset(wb,0,sizeof(wb));
for (int i=1;i<=n;i++)
wb[x[i]=s[i]]++;
for (int i=1;i<=m;i++)
wb[i]+=wb[i-1];
for (int i=n;i;i--)
sa[wb[x[i]]--]=i;
for (int j=1;p<n;j*=2,m=p)
{
p=0;
for (int i=n-j+1;i<=n;i++) y[++p]=i;
for (int i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (int i=1;i<=m;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=m;i++) wb[i]+=wb[i-1];
for (int i=n;i;i--) sa[wb[x[y[i]]]--]=y[i];
for (int i=1;i<=n;i++) swap(x[i],y[i]);
p=1,x[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p;
else x[sa[i]]=++p;
}
}
for (int i=1;i<=n;i++)
rk[sa[i]]=i;
int k=0;
for (int i=1;i<=n;i++)
{
if (k) k--;
if (rk[i]==1) continue;
int j=sa[rk[i]-1];
while (s[j+k]==s[i+k]) k++;
height[rk[i]]=k;
}
}
int main()
{
int T;
scanf("%d",&T);
getchar();
while (T--)
{
memset(s,0,sizeof(s));
string s1,s2;
s1.clear();s2.clear();
getline(cin,s1);
getline(cin,s2);
int pos=s1.size();
int n=0;
for (int i=0;i<(int)s1.size();i++)
s[++n]=s1[i];
s[++n]='~';
for (int i=0;i<(int)s2.size();i++)
s[++n]=s2[i];
build_sa();
int ans=0;
for (int i=2;i<=n;i++)
if ((sa[i]<pos)!=(sa[i-1]<pos))
ans=max(ans,height[i]);
printf("Nejdelsi spolecny retezec ma delku %d.\n",ans);
}
return 0;
}
题目链接
求一个字符串中所有既是前缀又是后缀的串在字符串中出现的次数。
首先求出原串的后缀数组和高度数组。
然后枚举长度,哈希判断前后是否相等。
在高度数组上二分,查询这一段区间的 h e i g h t [ ] height[] height[]的最小值是否比枚举的长度大。
显然可以发现所有前缀是同一个字符串的后缀必定是连续的。
考虑只有 r k [ 1 ] rk[1] rk[1]在这段区间里的才能进行统计(否则和前缀不相等了)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define pa pair
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
const int MAXN=100010;
int wb[MAXN],x[MAXN],y[MAXN];
int sa[MAXN],height[MAXN],rk[MAXN];
char s[MAXN];
void build_sa()
{
int n=strlen(s+1),m=10000,p=0;
for (int i=1;i<=n;i++) wb[x[i]=s[i]]++;
for (int i=1;i<=m;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[i]]--]=i;
for (int j=1;p<n;j*=2,m=p)
{
p=0;
for (int i=n-j+1;i<=n;i++) y[++p]=i;
for (int i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (int i=1;i<=m;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=m;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
for (int i=1;i<=n;i++) swap(x[i],y[i]);
p=1,x[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p;
else x[sa[i]]=++p;
}
}
for (int i=1;i<=n;i++)
rk[sa[i]]=i;
int k=0;
for (int i=1;i<=n;i++)
{
if (k) k--;
if (rk[i]==1) continue;
int j=sa[rk[i]-1];
while (s[j+k]==s[i+k]) k++;
height[rk[i]]=k;
}
}
vector <pa> ans;
int f[4*MAXN];
void build(int root,int left,int right)
{
if (left==right)
{
f[root]=height[left];
return ;
}
int mid=(left+right)>>1;
build(2*root,left,mid);
build(2*root+1,mid+1,right);
f[root]=min(f[2*root],f[2*root+1]);
}
int query(int root,int left,int right,int qleft,int qright)
{
if (qleft<=left && right<=qright)
return f[root];
int mid=(left+right)>>1;
if (qright<=mid)
return query(2*root,left,mid,qleft,qright);
else if (qleft>mid)
return query(2*root+1,mid+1,right,qleft,qright);
else
{
int ans1,ans2;
ans1=query(2*root,left,mid,qleft,mid);
ans2=query(2*root+1,mid+1,right,mid+1,qright);
return min(ans1,ans2);
}
}
int n;
bool check(int pos,int mid,int len)
{
int st=pos,ed=pos+mid-1;
if (ed<=st) return true;
if (ed>n) return false;
int qu=query(1,2,n,st+1,ed);
return qu>=len;
}
ull hsh[MAXN],p[MAXN];
#define P 2333
ull get_hsh(int st,int len)
{
return hsh[st+len-1]-hsh[st-1]*p[len];
}
int main()
{
scanf("%s",s+1);
build_sa();
n=strlen(s+1);
for (int i=1;i<=n;i++)
hsh[i]=hsh[i-1]*P+(s[i]-'a'+1);
p[0]=1;
for (int i=1;i<=n;i++)
p[i]=p[i-1]*P;
if (n==1)
{
cout<<1<<endl;
cout<<1<<' '<<1;
return 0;
}
build(1,2,n);
int pos=rk[1];
for (int i=n;i>1;i--)
{
if (rk[i]>pos) continue;
int l=1,r=n,anss,mid,len=n-i+1;
if (get_hsh(1,len)!=get_hsh(i,len)) continue;
while (l<=r)
{
mid=(l+r)>>1;
if (check(rk[i],mid,len)) anss=mid,l=mid+1;
else r=mid-1;
}
if (rk[i]+anss>pos) ans.push_back(mp(len,anss));
}
ans.push_back(mp(n,1));
printf("%d\n",(int)ans.size());
for (int i=0;i<ans.size();i++)
printf("%d %d\n",ans[i].fi,ans[i].se);
return 0;
}
/*
AAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAA
*/