【BZOJ3879】SvT(后缀数组)(单调队列)

本文介绍了一种使用后缀数组和最长公共前缀(LCP)解决字符串问题的方法。通过后缀排序预处理LCP,并利用单调队列在合并过程中计算答案。此方法简洁高效,适用于多种字符串匹配和搜索问题。

传送门


题解:

后缀排序,预处理LCP询问。

单调队列,合并两个块的时候算答案。

没了。

好写常数还小。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int get_s(char *s){
		int len=0;char c;
		while(isspace(c=gc()));
		while(s[len++]=c,!isspace(c=gc())&&c!=EOF);
		s[len]='\0';return len;
	}
	
	template<typename T>
	inline T get(){
		char c;
		while(!isdigit(c=gc()));T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=5e5+5;

int n;
char s[N];
int sa[N],rk[N],ht[N];

inline void radix_sort(int *x,int *y,int m,int n){
	static int bin[N];
	memset(bin+1,0,sizeof(int)*m);
	for(int re i=1;i<=n;++i)++bin[x[i]];
	for(int re i=1;i<=m;++i)bin[i]+=bin[i-1];
	for(int re i=n;i;--i)sa[bin[x[y[i]]]--]=y[i];
}

inline void init(){
	int *x=rk,*y=ht;
	for(int re i=1;i<=n;++i)x[i]=s[i],y[i]=i;
	radix_sort(x,y,128,n);
	int m=128;
	for(int re i=1,cnt=0;cnt<n;i<<=1){
		cnt=0;
		for(int re j=n-i+1;j<=n;++j)y[++cnt]=j;
		for(int re j=1;j<=n;++j)if(sa[j]>i)y[++cnt]=sa[j]-i;
		radix_sort(x,y,m,n);std::swap(x,y);
		x[sa[1]]=1,cnt=1;
		for(int re j=2;j<=n;++j)
		x[sa[j]]=(y[sa[j]]==y[sa[j-1]]&&y[sa[j]+i]==y[sa[j-1]+i])?cnt:++cnt;
		m=cnt;
	}
	for(int re i=1;i<=n;++i)rk[sa[i]]=i;
	for(int re i=1,j,k=0;i<=n;ht[rk[i++]]=k)
	for(k?--k:0,j=sa[rk[i]-1];s[i+k]==s[j+k];++k);
}

int mn[19][N],Log[N];

inline void init_RMQ(){
	Log[0]=-1;
	for(int re i=1;i<=n;++i)mn[0][i]=ht[i],Log[i]=Log[i>>1]+1;
	for(int re i=1;(1<<i)<=n;++i)
	for(int re j=1;j+(1<<i)-1<=n;++j)
	mn[i][j]=std::min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
}

inline int qy(int l,int r){
	int t=Log[r-l+1];
	return std::min(mn[t][l],mn[t][r-(1<<t)+1]);
}

inline int lcp(int a,int b){
	a=rk[a],b=rk[b];
	if(a>b)std::swap(a,b);
	return qy(a+1,b);
}

int m;
int q[N],h[N],cnt[N];
int st[N],top;
inline bool cmp(int a,int b){return rk[a]<rk[b];}

signed main(){
//	freopen("svt.in","r",stdin);
	n=getint(),m=getint();
	get_s(s+1),init(),init_RMQ();
	while(m--){
		n=getint();
		for(int re i=1;i<=n;++i)q[i]=getint();
		std::sort(q+1,q+n+1,cmp);
		n=std::unique(q+1,q+n+1)-q-1;
		for(int re i=1;i<n;++i)h[i]=lcp(q[i],q[i+1]);h[n]=-1;
		for(int re i=0;i<n;++i)cnt[i]=1;
		top=0;ll ans=0;
		for(int re i=1;i<=n;++i){
			while(top&&h[i]<=h[st[top]]){
				ans+=(ll)h[st[top]]*cnt[st[top-1]]*cnt[st[top]];
				cnt[st[top-1]]+=cnt[st[top]];
				--top;
			}
			st[++top]=i;
		}
		cout<<ans<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值