BZOJ 3230 相似子串 后缀数组+二分+ST表

来源:互联网 发布:10大网络恐怖推理图 编辑:程序博客网 时间:2024/06/06 06:57

Description

Input

输入第1行,包含3个整数N,Q。Q代表询问组数。
第2行是字符串S。
接下来Q行,每行两个整数i和j。(1≤i≤j)。

Output

输出共Q行,每行一个数表示每组询问的答案。如果不存在第i个子串或第j个子串,则输出-1。

Sample Input

5 3
ababa
3 5
5 9
8 10

Sample Output

18
16
-1

HINT

样例解释

第1组询问:两个子串是“aba”,“ababa”。f = 32 + 32 = 18。

第2组询问:两个子串是“ababa”,“baba”。f = 02 + 42 = 16。

第3组询问:不存在第10个子串。输出-1。


数据范围

N≤100000,Q≤100000,字符串只由小写字母'a'~'z'组成



传送门

首先把原来的串建一遍后缀数组,

接着可以求出子串数目的前缀和。

即按照rank下来,sum[i]表示前i个后缀有sum[i]个不同的子串。

这个过程很简单,因为相同的子串是height[i]个,统计即可。

然后要求出排名为x的子串,只要在sum数组里面二分即可;


利用这个方法,我们可以十分快速地求出给出的两个子串的具体位置。

既然知道了具体位置(假设分别是L1~R1,L2~R2),

那么它们的LCP其实就是:

R1-L1+1

R2-L2+1

min(height[rank[L1]+1..rank[L2]])

以上三者的最小值。应该很好理解的。

对了要注意L1=L2的情况,第三个要特判的。

为了求出第三个,我们需要额外建立一个st表以便O(1)查询。


那么最长公共后缀呢?(LCS)

很简单,我们可以把原来的串反过来,然后求一遍sa。

接着的方法就跟上面的一样了。

建议开二维数组,比如sa[opt][],opt=0时存的是正序的sa,opt=1时是逆序的。

当然struct等等都可以。



#include<bits/stdc++.h>#define ll long longusing namespace std;ll read(){    ll x=(ll)0,f=(ll)1;char ch=getchar();    while (ch<'0' || ch>'9'){if (ch=='-') f=(ll)-1;ch=getchar();}    while (ch>='0' && ch<='9'){x=x*(ll)10+ch-'0';ch=getchar();}    return x*f;}const int N=100005,logN=20;char s[2][N];int n,Q,Len;int cnta[N],cntb[N],a[N],b[N<<1],tsa[N];int sa[2][N],rank[2][N],height[2][N];int st[2][N][logN];ll sumSub[2][N];void Get_SA(int x){for (int i=0;i<=25;i++) cnta[i]=0;for (int i=1;i<=Len;i++) cnta[s[x][i]-97]++;for (int i=1;i<=25;i++) cnta[i]+=cnta[i-1];for (int i=Len;i;i--) sa[x][cnta[s[x][i]-97]--]=i;rank[x][sa[x][1]]=1;for (int i=2;i<=Len;i++)rank[x][sa[x][i]]=rank[x][sa[x][i-1]]+(s[x][sa[x][i]]!=s[x][sa[x][i-1]]);for (int j=1;rank[x][sa[x][Len]]!=Len;j<<=1){for (int i=1;i<=Len;i++) a[i]=rank[x][i],b[i]=rank[x][i+j];for (int i=0;i<=Len;i++) cnta[i]=cntb[i]=0;for (int i=1;i<=Len;i++) cnta[a[i]]++,cntb[b[i]]++;for (int i=1;i<=Len;i++) cnta[i]+=cnta[i-1],cntb[i]+=cntb[i-1];for (int i=Len;i;i--) tsa[cntb[b[i]]--]=i;for (int i=Len;i;i--) sa[x][cnta[a[tsa[i]]]--]=tsa[i];rank[x][sa[x][1]]=1;for (int i=2;i<=Len;i++)rank[x][sa[x][i]]=rank[x][sa[x][i-1]]+(a[sa[x][i]]!=a[sa[x][i-1]] || b[sa[x][i]]!=b[sa[x][i-1]]);}}void Get_H(int x){int len=0;for (int i=1;i<=Len;i++){if (len) len--;while (s[x][i+len]==s[x][sa[x][rank[x][i]-1]+len]) len++;height[x][rank[x][i]]=len;}}void PreRMQ(int id){for (int i=1;i<=Len;i++)st[id][i][0]=i;for (int j=1;j<=logN;j++)for (int i=1;i<=Len;i++)if (i+(1<<j)-1>Len) break;elseif (height[id][st[id][i][j-1]]>height[id][st[id][i+(1<<(j-1))][j-1]])st[id][i][j]=st[id][i+(1<<(j-1))][j-1];elsest[id][i][j]=st[id][i][j-1];}void PreSUM(int x){sumSub[x][0]=(ll)0;for (int i=1;i<=Len;i++)sumSub[x][i]=sumSub[x][i-1]+(ll)(Len-height[x][i]-sa[x][i]+1);}int RMQ(int id,int x,int y){int k=(int)((double)log(y-x+1)/(double)log(2));if (height[id][st[id][x][k]]<height[id][st[id][y-(1<<k)+1][k]])return st[id][x][k]; elsereturn st[id][y-(1<<k)+1][k];}int BS(int x,ll y){int L=0,R=Len,mid;while (L<R){mid=(L+R+1)>>1;if (sumSub[x][mid]<=y) L=mid;else R=mid-1;}return L;}void Get_Place(ll &l1,ll &r1){int t1=BS(0,l1);if (l1==sumSub[0][t1]) l1=sa[0][t1],r1=Len;else r1=l1-sumSub[0][t1]+height[0][t1+1],l1=sa[0][t1+1],r1+=l1-1;}ll Get_ANS(int x,int l,int r){if (rank[x][l]>rank[x][r]) swap(l,r);if (l==r) return (ll)Len-l+1;return (ll)height[x][RMQ(x,rank[x][l]+1,rank[x][r])];}int main(){n=(int)read(),Q=(int)read();scanf("%s",s[0]+1);Len=strlen(s[0]+1);for (int i=1;i<=Len;i++) s[1][Len-i+1]=s[0][i];for (int i=0;i<2;i++)Get_SA(i),Get_H(i),PreRMQ(i),PreSUM(i);ll l1,l2,r1,r2,len1;while (Q--){l1=read(),l2=read();if (l1>sumSub[0][Len] || l2>sumSub[0][Len]){puts("-1");continue;}Get_Place(l1,r1),Get_Place(l2,r2);len1=r1-l1+(ll)1,len1=min(len1,r2-l2+(ll)1);r1=Len-r1+(ll)1,r2=Len-r2+(ll)1;ll A=Get_ANS(0,l1,l2),B=Get_ANS(1,r1,r2);A=min(A,len1),B=min(B,len1);printf("%lld\n",A*A+B*B);}return 0;}

原创粉丝点击