bzoj 3879 SvT

LINK:SvT

给出一个字符串 给出若干个后缀 求两两后缀之间的LCP.

考虑SAM 要把字符串反着建立SAM 考虑两个后缀的LCP 其实就是在parent树上两点的LCA的长度.

可以发现建立出虚树 在虚树上跑一遍dp即可。复杂度询问数*log.

考虑SA 直接求出SA 按照各个后缀的排名排序加入对答案的贡献中 利用单调队列来维护新加入的后缀和之前的后缀的LCP 即可O(1)计算答案.

不过需要ST表查两个后缀之间的LCP.

代码复杂度的话 两者差不多 SAM还是要简单一些的 所以这里考虑写SA /cy.

const int MAXN=500010<<1;
int n,m,Q,top;
char a[MAXN];
int f[MAXN][21],b[MAXN*3],Log[MAXN],s[MAXN],w[MAXN];
int x[MAXN],y[MAXN],h[MAXN],c[MAXN],sa[MAXN],rk[MAXN];
inline void SA()
{
	m=150;
	rep(1,n,i)++c[x[i]=a[i]];
	rep(1,m,i)c[i]+=c[i-1];
	rep(1,n,i)sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k=k<<1)
	{
		int num=0;
		rep(n-k+1,n,i)y[++num]=i;
		rep(1,n,i)if(sa[i]>k)y[++num]=sa[i]-k;
		rep(1,m,i)c[i]=0;
		rep(1,n,i)++c[x[i]];
		rep(1,m,i)c[i]+=c[i-1];
		fep(n,1,i)sa[c[x[y[i]]]--]=y[i];
		rep(1,n,i)y[i]=x[i],x[i]=0;
		x[sa[1]]=num=1;
		rep(2,n,i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
		if(num==n)break;
		m=num;
	}
	rep(1,n,i)rk[sa[i]]=i;
}
inline int cmp(int x,int y){return rk[x]<rk[y];}
inline void get_Height()
{
	int k=0;
	rep(1,n,i)
	{
		if(rk[i]==1)continue;
		if(k)--k;
		int j=sa[rk[i]-1];
		while(a[i+k]==a[j+k])++k;
		h[rk[i]]=k;
	}
}
inline int LCP(int x,int y)
{
	x=rk[x];y=rk[y];
	--y;int w=Log[y-x+1];
	return min(f[x][w],f[y-(1<<w)+1][w]);
}
int main()
{
	freopen("1.in","r",stdin);
	gt(n);gt(Q);gc(a);
	SA();get_Height();
	rep(2,n,i)
	{
		f[i-1][0]=h[i];
		Log[i]=Log[i>>1]+1;
	}
	rep(1,Log[n-1],j)
		rep(1,n-(1<<j),i)f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	rep(1,Q,i)
	{
		int get(x);ll cnt=0,ans=0,last=0;
		rep(1,x,j)get(b[j]);
		sort(b+1,b+1+x,cmp);
		top=0;last=b[1];
		rep(2,x,j)
		{
			if(b[j]==b[j-1])continue;
			int len=LCP(last,b[j]),num=1;
			while(top&&len<=s[top])
			{
				cnt-=(ll)s[top]*w[top];
				num+=w[top];--top;
			}
			s[++top]=len;cnt+=(ll)num*len;
			w[top]=num;ans+=cnt;last=b[j];
		}
		putl(ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/chdy/p/12599407.html