【BZOJ3879】SvT(后缀数组+单调栈)

点此看题面

大致题意: 给定一个字符串,每次询问给出若干后缀,求两两之间的(LCP)之和。

前言

明明是想练后缀自动机的,结果看到这题一眼想到后缀数组,而且一两分钟就推出了解法,因此最终默默地写了后缀数组。。。

不过真的好坑啊,题目最下面写了可能存在相同后缀,且相同后缀只计算一次答案。没看到这句话的我白白浪费了半个小时。

(LCP)的本质

考虑后缀数组中我们存下了每一个后缀,而这里的(LCP)正是指某两个后缀的最长前缀,与题目中的意思一致。

回忆一下我们是如何计算(LCP),然后就会发现,是依靠(RMQ)求出一段区间内(Height)的最小值。

这也正是后缀数组求(LCP)的本质。

单调栈

考虑这一题,我们按照后缀排序的排名枚举每一个后缀。

对于一个后缀,我们只计算排名在它之前的后缀,然后发现它们的(LCP)就是一段段右端点相同的区间的最小值之和。

那么只要开一个单调递增的单调栈维护每一段最小值,就可以维护出答案了。

每次只需要加入(LCP(a_{i-1},a_i))更新栈即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define LN 20
#define LL long long
#define X 23333333333333333
using namespace std;
int n,m,a[N+5],Sp[N+5],Sv[N+5];char s[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		I void reads(CI n,char *s) {W(isspace(c=tc()));for(RI i=1;i<=n;++i) s[i]=c,c=tc();}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('
');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class SuffixArray//后缀数组
{
	private:
		int p[N+5],rk[N+5],t[N+5],SA[N+5],H[N+5],Lg[N+5],Mn[N+5][LN+5];
		I void Sort(CI S)//基数排序
		{
			RI i;for(i=0;i<=S;++i) t[i]=0;for(i=1;i<=n;++i) ++t[rk[i]];
			for(i=1;i<=S;++i) t[i]+=t[i-1];for(i=n;i;--i) SA[t[rk[p[i]]]--]=p[i];
		}
		I void GetSA(char *s)//求出SA
		{
			RI i;for(i=1;i<=n;++i) rk[p[i]=i]=s[i];
			RI k,t=0,S='z';for(Sort(S),k=1;t^n;S=t,k<<=1)
			{
				for(t=0,i=1;i<=k;++i) p[++t]=n-k+i;
				for(i=1;i<=n;++i) SA[i]>k&&(p[++t]=SA[i]-k);
				for(Sort(S),i=1;i<=n;++i) p[i]=rk[i];
				for(rk[SA[1]]=t=1,i=2;i<=n;++i) rk[SA[i]]=
					(p[SA[i-1]]^p[SA[i]]||p[SA[i-1]+k]^p[SA[i]+k])?++t:t;
			}
		}
		I void GetH(char *s)//求出Height数组
		{
			RI i,j,k=0;for(i=1;i<=n;++i) p[SA[i]]=i;
			for(i=1;i<=n;++i)
			{
				if(k&&--k,p[i]==1) continue;j=SA[p[i]-1];
				W(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;Mn[p[i]][0]=H[p[i]]=k;
			}
			for(Lg[0]=-1,i=1;i<=n;++i) Lg[i]=Lg[i>>1]+1;//RMQ预处理
			for(j=1;j<=Lg[n];++j) for(i=1;i+(1<<j)-1<=n;++i)
				Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);
		}
	public:
		I int operator [] (CI x) Con {return p[x];}
		I void Init(char *s) {GetSA(s),GetH(s);}//初始化
		I int LCP(CI a,CI b)//其实这里就是利用其计算方式,求一段区间Heigth的min
		{
			RI x=min(p[a],p[b])+1,y=max(p[a],p[b]),k=Lg[y-x+1];
			return min(Mn[x][k],Mn[y-(1<<k)+1][k]);
		}
}S;
I bool cmp(CI x,CI y) {return S[x]<S[y];}//按后缀排名排序
int main()
{
	RI Qt,i,t,T;LL res,ans;F.read(n),F.read(Qt),F.reads(n,s),S.Init(s);
	Sp[0]=1;W(Qt--)
	{
		for(F.read(m),i=1;i<=m;++i) F.read(a[i]);
		if(sort(a+1,a+m+1,cmp),m=unique(a+1,a+m+1)-a-1,m==1) {F.writeln(0);continue;}//注意去重emmm
		for(res=ans=Sv[T=1]=S.LCP(a[1],a[2]),Sp[1]=2,i=3;i<=m;++i)//枚举右端点
		{
			#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
			#define Dec(x,y) ((x-=(y))<0&&(x+=X))
			t=S.LCP(a[i-1],a[i]);W(T&&Sv[T]>=t) Dec(res,1LL*Sv[T]*(Sp[T]-Sp[T-1])),--T;//维护栈的单调性,弹出元素时要更新答案
			Sv[++T]=t,Sp[T]=i,Inc(res,1LL*Sv[T]*(Sp[T]-Sp[T-1])),Inc(ans,res);//把LCP加入栈中,累加答案
		}F.writeln(ans);
	}return F.clear(),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3879.html