后缀数组的原理大致了解,至于那精简的代码却只能说知道每一步在做什么>_<
(ps:目前已知的文艺程序的代表作)
运用倍增的思想,每个点向后延伸2^i的排名可由2^(i-1)推出,因此用基排每次可在o(n)时间摆平,但是要扩展(logn)次,所以是o(nlogn)的
而height表示相邻两后缀的最长公共前缀,根据定理height[rank[i]]>=height[rank[i-1]]-1
求出height便可以做一系列题目(任意两后缀)
不可重叠最长重复子串(poj1743)
给定一个字符串,求最长重复子串,这两个子串不能重叠。
二分ans,然后按ans分组,每组内的任意两后缀一定能凑出一个重复字串,只需有一组(max and min)差过k,则有解
nlogn
#include <cstdio>
#include <cstdlib>
#include <cstring>
const int oo=1073741819;
int a[200060],b[200060],sa[200060],rk[200060],ht[200060],ws[200060],wv[200060],y[200060],ans,l,n;
void dag(int l,int m)
{
int i,j,p;
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[rk[i]=b[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
for (j=1,p=0;p<l;j<<=1,m=p)
{
for (p=0,i=l-j+1;i<=l;i++) y[++p]=i;
for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (i=1;i<=l;i++) wv[i]=rk[y[i]];
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[wv[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i];
for (i=1;i<=l;i++) y[i]=rk[i];
for (rk[sa[1]]=1,p=1,i=2;i<=l;i++)
rk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+j]==y[sa[i-1]+j])) ? p : ++p;
}
}
void hig(int l)
{
int i,j,p;
for (i=1,p=0;i<=l;ht[rk[i++]]=p)
for (p ? --p : 0,j=sa[rk[i]-1];b[i+p]==b[j+p];++p) ;
}
int check(int k)
{
int i,min,max;
for (i=2,min=sa[1],max=sa[1];i<=l;i++)
{
if (ht[i]>=k)
{
if (sa[i]<min) min=sa[i];
if (sa[i]>max) max=sa[i];
if (sa[i-1]<min) min=sa[i-1];
if (sa[i-1]>max) max=sa[i-1];
if (max-min>k) return 1;
}
else {
if (max-min>k) return 1;
else {max=-oo,min=oo;}
}
}
return 0;
}
void make()
{
int l=1,r=n,mid;
for (;l<=r;)
{
mid=(l+r)>>1;
if (check(mid)) l=mid+1;
else r=mid-1;
}
ans=l-1;
}
void init()
{
int i;
ans=0;
memset(a,0,sizeof(a));memset(b,0,sizeof(b));
for (i=1;i<=l;i++) scanf("%d",&a[i]);
scanf("\n");
for (i=1;i<l;i++) b[i]=a[i]-a[i+1]+89;
// b[l]=0;
l--;n=l;
dag(l,1000);
hig(l);
make();
if (ans+1>=5) printf("%d\n",ans+1); else printf("0\n");
}
int main()
{
freopen("1743.in","r",stdin);
freopen("1743.out","w",stdout);
for (;;)
{
scanf("%d\n",&l);
if (0==l) break;
init();
}
return 0;
}
最长公共子串(pku2774,ural1517)
给定两个字符串 A 和 B,求最长公共子串。
求出height后,因为对于一个后缀的最长公共前缀,离他越远会越短,所以答案在所有height中最大一个(相邻且分属于不同字符串)
#include <cstdio>
#include <cstdlib>
#include <cstring>
char s[205000],s1[105000],s2[105000];
int wv[205000],ws[205000],y [205000],rk[205000],sa[205000],ht[205000];
int l,ans;
void dag(int l,int m)
{
int i,j,p;
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[rk[i]=s[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
for (j=1,p=0;p<l;j<<=1,m=p)
{
for (p=0,i=l-j+1;i<=l;i++) y[++p]=i; //第二关键字为0
for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (i=1;i<=l;i++) wv[i]=rk[y[i]];
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[wv[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i]; //wv[i]=rk[y[i]];
for (i=1;i<=l;i++) y[i]=rk[i]; //y[i]=oldrk[i];
for (rk[sa[1]]=1,i=2,p=1;i<=l;i++)
rk[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p;
}
}
void hig(int l)
{
int i,j,p=0;
for (i=1,p=0;i<=l;ht[rk[i++]]=p) //ht[rk[i]]>=ht[rk[i-1]]-1
for (p?p--:0,j=sa[rk[i]-1];s[j+p]==s[i+p];)
p++
; //ht[1]无用
}
void init()
{
int l1,l2,i;
memset(s1,0,sizeof(s1));
memset(s2,0,sizeof(s2));
memset(s ,0,sizeof(s ));
scanf("%s\n",s1+1);l1=strlen(s1+1);
scanf("%s\n",s2+1);l2=strlen(s2+1);
for (l=1;l<=l1;l++) s[l]=s1[l];
l=l1+1;s[l]='_';
for (++l;l<=l1+l2+1;l++) s[l]=s2[l-l1-1];
s[l]='_';
dag(l,'z');
hig(l);
ans=0;
for (i=2;i<=l;i++)
{
if (((sa[i]<=l1)&&(sa[i-1]>l1+1))||((sa[i]>l1+1)&&(sa[i-1]<=l1)))
if (ht[i]>ans) ans=ht[i];
}
printf("%d\n",ans);
}
int main()
{
freopen("2774.in","r",stdin);
freopen("2774.out","w",stdout);
init();
return 0;
}
可重叠的 k 次最长重复子串(pku3261)
给定一个字符串,求至少出现 k 次的最长重复子串,这 k 个子串可以重叠。
二分答案,然后扫描判断。
#include <cstdio>
#include <cstdlib>
#include <cstring>
int ws[1000500],a[50000],y[50000],wv[50000],sa[50000],rk[50000],ht[50000],ans,n,m,st[50000],k;
void dag(int l,int m)
{
int i,j,p;
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[rk[i]=a[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
for (j=1,p=0;p<l;j<<=1,m=p)
{
for (p=0,i=l-j+1;i<=l;i++) y[++p]=i;
for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (i=1;i<=l;i++) wv[i]=rk[y[i]];
for (i=1;i<=m;i++) ws[i]=0;
for (i=1;i<=l;i++) ws[wv[i]]++;
for (i=1;i<=m;i++) ws[i]+=ws[i-1];
for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i];
for (i=1;i<=l;i++) y[i]=rk[i];
for (rk[sa[1]]=1,p=1,i=2;i<=l;i++)
rk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p) ;
}
}
void hig(int l)
{
int i,j,p;
for (p=0,i=1;i<=l;ht[rk[i++]]=p)
for (p ? --p : p,j=sa[rk[i]-1];a[j+p]==a[i+p];++p) ; //j鏄瘮杈?
}
int check(int p)
{
int i,j,ll,hh;
ll=0,hh=0;
for (i=1,j=1;i<=n;i++)
{
for (;(j+1<=n)&&(j-i+1<k);)
{
j++;
for (;(ll>hh)&&(st[ll]>ht[j]);ll--) ;
st[++ll]=ht[j];
}
if (j-i+1<k) return 0;
if (st[hh+1]>=p) return 1;
if (st[hh+1]==ht[i+1]) hh++;
}
}
void make(int n)
{
int l=1,r=n,mid;
for (;l<=r;)
{
mid=(l+r)>>1;
if (check(mid)) l=mid+1;
else r=mid-1;
}
ans=l-1;
}
void init()
{
int i;
scanf("%d%d\n",&n,&k);
for (i=1;i<=n;i++) {scanf("%d\n",&a[i]);a[i]++;}
dag(n,1000000+5);
hig(n);
make(n);
printf("%d\n",ans);
}
int main()
{
freopen("3261.in","r",stdin);
freopen("3261.out","w",stdout);
init();
return 0;
}