[BZOJ 3230]相似子串

[BZOJ 3230]相似子串

题意

给定一个长度为 \(n\) 的字符串以及 \(q\) 组查询, 每组查询给定 \(a\)\(b\), 求在所有本质不同子串中排名第 \(a\) 和第 \(b\) 的串的最长公共前缀与最长公共后缀的平方和.

\(n,q\le 1\times 10^5\).

题解

后缀数组板子题.麻麻我终于会用后缀数组辣

本来想接着用SAM的...但是发现多组查询排名为 \(k\) 的本质不同子串以我对SAM的理解好像不能做...于是就用了SA.

最长公共前后缀显然对正反串建两个SA就可以 \(O(n\log n)\rightarrow O(1)\) 算了. 关键在于怎么找到这两个子串.

把所有后缀排序后, 显然每一个后缀对本质不同子串的贡献就是和前一个后缀相同的部分之外的部分了. 那么求出所有 \(height\) 后我们也能知道前 \(k\) 个后缀总共产生了多少本质不同的子串. 直接二分找到子串左端点然后根据差值算出子串长度就可以了.

因为要有两个SA, 所以简单地封到结构体里会比较好写. 然而这根本不算封装

参考代码

#include <bits/stdc++.h>

const int MAXN=1e5+10;
typedef long long intEx;

struct SuffixArray{
	int* x;
	int* y;
	char s[MAXN];
	int SA[MAXN];
	int lg[MAXN];
	int cnt[MAXN];
	int rank[MAXN];
	intEx sum[MAXN];
	int height[MAXN];
	int st[18][MAXN];
	SuffixArray():x(new int[MAXN]),y(new int[MAXN]){}
	void Build();
	int LCP(int,int);
	std::pair<int,int> Query(intEx);
};
SuffixArray a,b;

int n;
int q;

inline intEx Sqr(intEx);

int main(){
	scanf("%d%d",&n,&q);
	scanf("%s",a.s+1);
	for(int i=1;i<=n;i++)
		b.s[i]=a.s[i];
	std::reverse(b.s+1,b.s+n+1);
	a.Build();
	b.Build();
	for(int i=0;i<q;i++){
		intEx x,y;
		scanf("%lld%lld",&x,&y);
		auto p1=a.Query(x);
		auto p2=a.Query(y);
		if(p1.first==-1||p2.first==-1)
			puts("-1");
		else{
			intEx ans=Sqr(std::min(a.LCP(p1.first,p2.first),std::min(p1.second,p2.second)));
			p1.first=n-(p1.first+p1.second-1)+1;
			p2.first=n-(p2.first+p2.second-1)+1;
			ans+=Sqr(std::min(b.LCP(p1.first,p2.first),std::min(p1.second,p2.second)));
			printf("%lld\n",ans);
		}
	}
	return 0;
}

int SuffixArray::LCP(int x,int y){
	if(x==y)
		return INT_MAX;
	else{
		int l=rank[x],r=rank[y];
		if(l>r)
			std::swap(l,r);
		++l;
		int len=r-l+1;
		return std::min(st[lg[len]][l],st[lg[len]][r-(1<<lg[len])+1]);
	}
}

std::pair<int,int> SuffixArray::Query(intEx k){
	int p=std::lower_bound(sum+1,sum+n+1,k)-sum;
	if(p==n+1)
		return {-1,-1};
	else
		return {SA[p],n-(sum[p]-k)-SA[p]+1};
}

void SuffixArray::Build(){
	int m=127;
	for(int i=1;i<=n;i++)
		++cnt[x[i]=s[i]];
	for(int i=1;i<=m;i++)
		cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)
		SA[cnt[x[i]]--]=i;
	for(int k=1;k<n;k<<=1){
		int p=0;
		for(int i=n-k+1;i<=n;i++)
			y[++p]=i;
		for(int i=1;i<=n;i++)
			if(SA[i]>k)
				y[++p]=SA[i]-k;
		memset(cnt+1,0,sizeof(int)*m);
		for(int i=1;i<=n;i++)
			++cnt[x[i]];
		for(int i=1;i<=m;i++)
			cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--)
			SA[cnt[x[y[i]]]--]=y[i];
		std::swap(x,y);
		x[SA[1]]=1;
		p=1;
		for(int i=2;i<=n;i++)
			x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+k]==y[SA[i-1]+k])?p:++p;
		if(p>=n)
			break;
		m=p;
	}
	for(int i=1;i<=n;i++)
		rank[SA[i]]=i;
	int k=0;
	for(int i=1;i<=n;i++){
		if(rank[i]==1)
			continue;
		if(k!=0)
			--k;
		int j=SA[rank[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])
			++k;
		height[rank[i]]=k;
		st[0][rank[i]]=k;
	}
	for(int i=1;i<=n;i++)
		sum[i]=sum[i-1]+(n-SA[i]+1)-height[i];
	for(int i=1;(1<<i)<=n;i++){
		++lg[1<<i];
		for(int j=2;j<=n;j++){
			st[i][j]=st[i-1][j];
			if(j+(1<<(i-1))<=n)
				st[i][j]=std::min(st[i][j],st[i-1][j+(1<<(i-1))]);
		}
	}
	for(int i=1;i<=n;i++)
		lg[i]+=lg[i-1];
}

inline intEx Sqr(intEx x){
	return x*x;
}

posted @ 2019-03-26 21:27  rvalue  阅读(251)  评论(0编辑  收藏  举报