1. 1. 后缀:包含最后一个字符的字串。
2. 2. 前缀:包含第一个字符的字串。
3. 3. 子串:字符串中连续的某一段。等价定义:某个后缀的前缀
4.sa[i] 4. s a [ i ] 表示排名为 i i 的后缀的起始位置。
5.rank[i] 5. r a n k [ i ] 表示起始位置为 i i 的后缀的排名。
6.lcp(i,j) 6. l c p ( i , j ) 表示起始位置为 i i 的后缀和起始位置为 j j 的后缀的最长公共前缀。
7.height[i]=lcp(sa[i],sa[i−1]) 7. h e i g h t [ i ] = l c p ( s a [ i ] , s a [ i − 1 ] )
#include
#include
#include
#define maxn 2000050
using namespace std;
int wa[maxn],wb[maxn],rs[maxn];
int sa[maxn],rank[maxn],height[maxn];
#define cmp(r,a,b,l) ((r[a]==r[b])&&(r[a+l]==r[b+l]))
void da(const char *r,int n,int m){
int *x=wa,*y=wb;
for(int i=1;i<=n;++i)
++rs[x[i]=r[i]];
for(int i=2;i<=m;++i)
rs[i]+=rs[i-1];
for(int i=n;i>=1;--i)
sa[rs[x[i]]--]=i;
for(int j=1,p=0;j<=n&&p1,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) rs[i]=0;
for(int i=1;i<=n;++i) ++rs[x[i]];
for(int i=2;i<=m;++i) rs[i]+=rs[i-1];
for(int i=n;i>=1;--i)
sa[rs[x[y[i]]]--]=y[i];
swap(x,y);
x[sa[1]]=1;
p=1;
for(int i=2;i<=n;++i)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p:++p;
}
for(int i=1; i<=n; ++i)
rank[sa[i]]=i;
for(int i=1,k=0; i<=n; height[rank[i++]]=(k?k--:k))
for(int j=sa[rank[i]-1]; r[i+k]==r[j+k]; ++k);
}
以下代码省略模板。
char r[maxn];
int main()
{
scanf("%s",r+1);
da(r,strlen(r+1),300);
for(int i=1;r[i];i++)
printf("%d ",sa[i]);
}
题意:给定一个字符串,求最长重复子串,这两个子串可以重叠。
题解:对这个字符串求后缀数组,由于子串必然是某个后缀的前缀,因此可以得知:只有排序后相邻的两个后缀的的最长公共前缀才可能是答案。因此答案即为 maxi=1nheight[i] max i = 1 n h e i g h t [ i ] 。
代码:
char s[maxn];
int main(void)
{
scanf("%s",s+1);
const int l=strlen(s+1);
da(s,l,127);
int ans=0;
for(int i=1;i<=l;i++)
ans=max(ans,height[i]);
printf("%d\n",ans);
return 0;
}
题意:给定一个序列,求等价的最长子串,且长度大于 5 5 ,不可重叠。
等价的定义:对序列整体加上某个数值后与另一个序列完全相等。
题解:可以发现,两个原序列的长度为 l+1 l + 1 的等价序列,差分后,为长度为 l l 的相等序列。因此问题转换为不可重叠的最长重复子串。
二分答案 k k ,问题转换为判定是否存在长度为 k k 的最长子串。如果继续沿用上一题的方法会出现重叠,因此对height数组分组,所有相邻的大于 k k 的 height h e i g h t 分为一组,当 k k 等于 2 2 时候,分组如下。
可以发现, 有希望成为最长公共前缀不小于k的两个后缀一定在同一组。且两个后缀不会重叠当且仅当它们的 sa s a 值的差大于 k k 。
代码:
int n,a[MAXN],r[MAXN];
bool check(int k) {
bool flag=0;
int mx=-INF,mm=INF;
for(int i=2; i<=n; ++i) {
if(height[i]>=k) {
//最有可能成功的是组内最长和最短的
mm=min(mm,min(sa[i],sa[i-1]));
mx=max(mx,max(sa[i],sa[i-1]));
if(mx-mm-1>=k) return 1;
//差分后,不重叠的长度需要加一
} else {
mx=-INF,mm=INF;
}
}
return 0;
}
int main() {
while(~scanf("%d",&n) && n) {
for(int i=0; iscanf("%d",a+i);
--n;
for(int i=0; i1]-a[i]+88;//差分
r[n]=0;
SA(r,n+1,176);
int l=0,r=n>>1,ans=0;
while(lint mid=(l+r+1)>>1;
if(check(mid))
l=ans=mid;
else
r=mid-1;
}
if(l>=4)//总长度大于等于5,故差分长度大于等于4
printf("%d\n",ans+1);
else
printf("%d\n",0);
}
return 0;
}
题意:给定一个序列,求序列中至少出现 k k 次的可重叠的最长子串。
题解:类似上一题,二分答案长度 l l ,对 height h e i g h t 数组分组,不小于 l l 的分在一个组内,判断是否有一个组的大小至少为 k k 即可。
代码:
struct node{
int d,i;
bool operator<(const node &x)const{
return d20020];
int t[20020],n,k;
bool check(int mid){
int cnt=0;
for(int i=2;i<=n;i++){
if(height[i]>=mid){//大括号不可省略,否则下面的else会被认为是下面的if的。
if(++cnt>=k-1)//n个height元素表示n+1个串的LCP至少为k
return true;
}else
cnt=0;
}
return false;
}
int main(void)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&m[i].d),m[i].i=i;
sort(m+1,m+n+1);//数据范围过大,离散化
int cnt=0,last=0;
for(int i=1;i<=n;i++){
if(m[i].d==last)
t[m[i].i]=cnt;
else
last=m[i].d,t[m[i].i]=++cnt;
}
da(t,n,20010);
int l=1,r=n,ans=0;
while(l<=r){//二分答案
int mid=(l+r)>>1;
if(check(mid))
l=(ans=mid)+1;
else
r=mid-1;
}
printf("%d",ans);
return 0;
}
题意:给定一个字符串,求不相同的子串的个数。
题解:只考虑后缀的前缀,则排第 k k 名的后缀有 n−sa[k]+1 n − s a [ k ] + 1 个前缀,但其中有 height[k] h e i g h t [ k ] 个前缀和上一个前缀相等,故有 n−sa[k]+1−height[k] n − s a [ k ] + 1 − h e i g h t [ k ] 个子串。
代码:
int T;
char s[maxn];
int main(void){
scanf("%d",&T);
while(T--){
scanf("%s",s+1);
const int l=strlen(s+1);
da(s,l,127);
int ans=0;
for(int i=1;i<=l;i++)
ans+=l-sa[i]+1-height[i];
printf("%d\n",ans);
}
return 0;
}