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

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 long
using 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;
				else
				if (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];
						else
					st[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]; else
		return 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值