后缀数组。
https://vjudge.net/contest/203023#overview
大约使用基数排序实现的 O(nlogn) 建立方法。
DC3比较麻烦就先不学了。
Hash+直接排序的方法在10行内完成。
复杂度为 O(nlog2n) 。
算是也可以用吧。
求不同的子串个数。
根据SA的定义,可以比较简单地求出重复的子串个数,即Height值的和。
struct SA{
int Ht[M<<1],Rk[M<<1],sa[M];
int C[M];
void Solve(){
int *x=Rk,*y=Ht;
memset(Cnt,0,sizeof(Cnt));
memset(Rk,0,sizeof(Rk));
memset(Ht,0,sizeof(Ht));
REP(i,0,n)Cnt[x[i]=C[i]]++;
REP(i,1,M)Cnt[i]+=Cnt[i-1];
REP(i,0,n)sa[--Cnt[x[i]]]=i;
for(int k=1;k<=n;k<<=1){
int p=0;
REP(i,0,n)Cnt[x[sa[i]]]=i+1;
REP(i,n-k,n)y[p++]=i;
REP(i,0,n)if(sa[i]>=k)y[p++]=sa[i]-k;
DREP(i,n-1,-1)sa[--Cnt[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=1;
REP(i,1,n)x[sa[i]]=x[sa[i-1]]+
(y[sa[i-1]]!=y[sa[i]] || y[sa[i-1]+k]!=y[sa[i]+k]);
}
int p=0,j;
memset(Rk,0,sizeof(Rk));
REP(i,0,n)Rk[sa[i]]=i;
REP(i,0,n)if(j=Rk[i]){
if(p)p--;j=sa[j-1];
while(C[i+p]==C[j+p])p++;
Ht[Rk[i]]=p;
}
Ht[0]=INF;
}
LL SumH(){
LL Res=0;
REP(i,1,n)Res+=Ht[i];
return Res;
}
}SA;
最长回文子串的SA写法。
将原串改为str+‘$’+rev(str)。
分类讨论求某两个位置的LCP。
ST表求RMQ得LCP。
struct SA{
int Ht[M<<1],Rk[M<<1],sa[M],RMQ[M][K];
char C[M];
int LCP(int l,int r){
if(l>r)swap(l,r);
l++;int k=Nm[r-l+1];//Nm[i]=log2i
return min(RMQ[l][k],RMQ[r-(1<1][k]);
}
void ReBuild(){
REP(i,0,n)RMQ[i][0]=Ht[i];
REP(k,1,K)REP(i,0,n)if((j=i+(1<1))1],RMQ[j][k-1]);
}else break;
}
void Answer(){
int Ans=0,Pos=-1;
REP(i,0,m){
int l=Query(Rk[i],Rk[n-i-1]);
if(chkmax(Ans,(l<<1)-1))Pos=i-l+1;
}
REP(i,1,m)if(C[i]==C[i-1]){
int l=Query(Rk[i],Rk[n-i]);
if(chkmax(Ans,l<<1))Pos=i-l;
}
REP(i,Pos,Pos+Ans)putchar(C[i]);
puts("");
}
}SA;
最大连续重复次数子串,字典序最小。
枚举长度,那么该串一定包含相邻该长度距离的两个字符。
分别求这两个字符向前向后的LCP,
即可得到重复次数,然后再通过对Rank值的RMQ求到字典序最小。
加了一些其他的优化。
inline int Cmp(const int &a,const int &b){
return SA1.Rk[a]int RKQ[M][K];
inline int Query(int l,int r){
int k=Nm[r-l+1];
return Cmp(RKQ[l][k],RKQ[r-(1<1][k]);
}
void Answer(){
int j;
REP(k,1,K) REP(i,0,n) if((j=i+(1<1))1],RKQ[j][k-1]);
int Ans=1,Pos=SA1.sa[0],Len=1;
REP(Lth,1,(n>>1)+1){
for(int i=0;i+Lthint x,y=0;
if(SA1.C[i]==SA1.C[i+Lth]){
x=SA2.LCP(n-i-1,n-i-Lth-1),y=SA1.LCP(i,i+Lth);
int Tmp=(x+y-1)/Lth+1;
if(Tmp>=Ans){
int Ltp=Tmp*Lth,ps=Query(i-x+1,i+y+Lth-Ltp);
if(Ans==Tmp){if(SA1.Rk[ps]else Ans=Tmp,Pos=ps,Len=Ltp;
}
}
i+=max(Lth,y);
}
}
printf("Case %d: ",++Case);
REP(i,Pos,Pos+Len)putchar(SA1.C[i]);
puts("");
}
最长公共子串。
即分属不同子串的相邻两个串的最大Height值。
struct SA{
void Answer(){
int Ans=0;
REP(i,1,n)if(Ans1]printf("%d\n",Ans);
}
}SA;
int main(){
scanf("%s",SA.C);
SA.C[m=strlen(SA.C)]='$';
scanf("%s",SA.C+m+1);
n=strlen(SA.C);
SA.Solve();
SA.Answer();
return 0;
}
求一段子串的不同子串的个数。
在询问时保证相邻两个串的字典序,求其LCP并减去。
#define IN(x) (l<=x && x<=r)
void Answer(){
int l,r;
scanf("%d%d",&l,&r);l--,r--;
int Len=r-l+1,Ans=Len*(Len+1)>>1,Lt=-1;
REP(i,0,n)if(IN(sa[i])){
if(Lt==-1)Lt=i;
else{
int Tmp=LCP(Lt,i);
int la=r-sa[Lt]+1,lb=r-sa[i]+1;
Ans-=min(min(la,lb),Tmp);
if(laif(!(--Len))break;
}
}
printf("%d\n",Ans);
}
求 max(LCP(Rk[i],Rk[j]))(j<i) 。
不会写就暴力。
二分答案,然后二分当前区间,求Rk最小值判断是否成立。
int Tmp;
bool Check(int Len,int i){
if(!Len)return 1;
int a,Lt,Rt,l,r;
a=Lt=Rt=Rk[i];
l=0,r=a-1;
while(l<=r){
int Mid=l+r>>1;
if(Query(RMQ,Mid+1,a)>=Len)Lt=Mid,r=Mid-1;
else l=Mid+1;
}
l=a+1,r=n;
while(l<=r){
int Mid=l+r>>1;
if(Query(RMQ,a+1,Mid)>=Len)Rt=Mid,l=Mid+1;
else r=Mid-1;
}
Tmp=Query(MK,Lt,Rt);
return Tmpfor(int i=0;iint l=0,r=n,Res=0;
while(l<=r){
int Mid=l+r>>1;
if(Check(Mid,i))Res=Mid,l=Mid+1;
else r=Mid-1;
}
if(!Res)printf("-1 %d\n",C[i]),i++;
else Check(Res,i),printf("%d %d\n",Res,Tmp),i+=Res;
}
}
然后正常的写法应该是单调栈。
当 i<j&&Ht[i]≥Ht[j] 时,保留i显然更优。