BZOJ3230 相似子串 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9033092.html
题目传送门 - BZOJ3230
题意
给定字符串$s$。长度为$n$。
现在有$Q$组询问,每组询问内容如下:
两个正整数$i,j$。
设$s_i,s_j$分别表示$s$的所有本质不同的子串中字典序第$i$小和第$j$小的子串。
请你输出$|lcp(s_i,s_j)|^2+|lcs(s_i,s_j)|^2$。
如果不存在$s_i$或者$s_j$,则输出$-1$。
$n,Q\leq 10^5$
题解
这题大概是我做的前几题$SA$的大整合+升级版吧。
我们考虑如何找到本质不同子串中第$k$大的子串。
我们考虑按照串$s$的后缀大小从小到大处理。
对于排名为$i$的后缀$SA[i]$,考虑它 除了与排名更靠前的其他后缀 的相同前缀以外的前缀所代表的子串。
很显然,这些子串都是本质不同的。根据后缀数组的性质,也很显然,这些串都不同于之前已经计算过的串。不然的话,$height[i]$会更大。
而且,这些子串是按照字典序排的。
现在,我们发现这些后,不需要处理出所有子串。
统计原串后缀排名前$i$的后缀所包含的子串的个数,记为$presum[i]$。
显然,$presum[i]=presum[i-1]+len(SA[i])-height[i]$。
于是我们在查找第$k$大子串的时候就可以通过二分或者倍增来快速地找到第$k$大的子串所在的后缀,然后确定第$k$大的子串就容易了。
至于求两个子串的$LCP$和$LCS$长度,是后缀数组的经典操作,这里就不加赘述了。
代码
#include <bits/stdc++.h> #define rank r_a_n_k using namespace std; typedef long long LL; const int N=200005; int n,Q; int SA[N],rank[N],height[N],tax[N],tmp[N]; int sSA[N],srank[N],sheight[N]; int ST[N][19]; LL presum[N]; char s[N]; void Sort(int n,int m,int SA[],int rank[]){ for (int i=0;i<=m;i++) tax[i]=0; for (int i=1;i<=n;i++) tax[rank[i]]++; for (int i=1;i<=m;i++) tax[i]+=tax[i-1]; for (int i=n;i>=1;i--) SA[tax[rank[tmp[i]]]--]=tmp[i]; } bool cmp(int rk[],int x,int y,int w){ return rk[x]==rk[y]&&rk[x+w]==rk[y+w]; } void Suffix_Array(char s[],int n,int SA[],int rank[],int height[]){ memset(SA,0,sizeof SA); memset(tmp,0,sizeof tmp); memset(rank,0,sizeof rank); memset(height,0,sizeof height); for (int i=1;i<=n;i++) rank[i]=s[i],tmp[i]=i; int m=127; Sort(n,m,SA,rank); for (int w=1,p=0;p<n;w<<=1,m=p){ p=0; for (int i=n-w+1;i<=n;i++) tmp[++p]=i; for (int i=1;i<=n;i++) if (SA[i]>w) tmp[++p]=SA[i]-w; Sort(n,m,SA,rank); for (int i=1;i<=n;i++) swap(rank[i],tmp[i]); rank[SA[1]]=p=1; for (int i=2;i<=n;i++) rank[SA[i]]=cmp(tmp,SA[i],SA[i-1],w)?p:++p; } for (int i=1,j,k=0;i<=n;height[rank[i++]]=k) for (k=max(k-1,0),j=SA[rank[i]-1];s[i+k]==s[j+k];k++); height[1]=0; } void Get_ST(int n){ memset(ST,0,sizeof ST); for (int i=1;i<=n;i++){ ST[i][0]=height[i]; for (int j=1;j<19;j++){ ST[i][j]=ST[i][j-1]; if (i-(1<<(j-1))>0) ST[i][j]=min(ST[i][j],ST[i-(1<<(j-1))][j-1]); } } } int Query(int L,int R){ int val=floor(log(R-L+1)/log(2)); return min(ST[L+(1<<val)-1][val],ST[R][val]); } int LCP(int x,int y){ if (x==y) return n; x=rank[x],y=rank[y]; return Query(min(x,y)+1,max(x,y)); } int LCS(int x,int y){ return LCP(n*2+2-x,n*2+2-y); } void GetSubstr(LL k,int &L,int &R){ int pos,i; for (pos=0,i=19;i>=0;i--) if (pos+(1<<i)<n&&presum[pos+(1<<i)]<k) pos+=(1<<i); L=sSA[pos+1]; R=1LL*L+k-presum[pos]+sheight[pos+1]-1; } int main(){ scanf("%d%d",&n,&Q); scanf("%s",s+1); s[n+1]='#'; for (int i=n*2+1;i>n+1;i--) s[i]=s[n*2+2-i]; Suffix_Array(s,n*2+1,SA,rank,height); Get_ST(n*2+1); for (int i=n+1;i<=n*2+1;i++) s[i]=0; Suffix_Array(s,n,sSA,srank,sheight); presum[0]=0; for (int i=1;i<=n;i++) presum[i]=presum[i-1]+(n-sSA[i]+1)-sheight[i]; while (Q--){ LL k1,k2; int L1,R1,L2,R2; scanf("%lld%lld",&k1,&k2); if (k1>presum[n]||k2>presum[n]){ puts("-1"); continue; } GetSubstr(k1,L1,R1); GetSubstr(k2,L2,R2); LL len=min(R1-L1+1,R2-L2+1); LL lcp=min(len,(LL)LCP(L1,L2)),lcs=min(len,(LL)LCS(R1,R2)); printf("%lld\n",lcp*lcp+lcs*lcs); } return 0; }