2019.01.01【AHOI2013】【BZOJ3238】【洛谷P4248】差异(后缀数组)

本文详细讲解了如何使用O(n)的时间复杂度构建后缀数组,特别是SA-IS算法,以及如何利用后缀数组高效求解两个后缀的最长公共前缀(LCP)问题。通过实例演示,展示了在具体问题中后缀数组的应用,包括代码实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BZOJ传送门

洛谷传送门


解析:

想要绝对的 O ( n ) O(n) O(n)过这道题,你必须要会后缀数组的 O ( n ) O(n) O(n)构建方法SA-IS,不要想着大常数的DC3能够跑的比SAM快。。。

但是 O ( n ) O(n) O(n)确实比 O ( n × ∣ A ∣ ) O(n\times |A|) O(n×A)快到不知道哪里去了。

目前本人洛谷rk1,BZOJrk2。
在我前面的是本校工程大佬xehoth,但是码长是我的两倍,不知道是不是用的指令集(滑稽。

思路:

显然求两个后缀的 L C P LCP LCP就是 h t ht ht数组的事情,我们自然而然的想到了SA。

首先把前面这个东西化简一下:
∑ 1 ≤ i &lt; j ≤ n l e n s u f i + l e n s u f j \sum_{1\leq i &lt; j \leq n}len_{suf_i}+len_{suf_j} 1i<jnlensufi+lensufj

显然每个串出现的时候都会和其他 n − 1 n-1 n1个串凑一次对,所以每个串需要被统计的次数就是 n − 1 n-1 n1,所有串的长度是一个从 1 1 1 n n n的排列,所以上面这个式子的值就是: n ( n + 1 ) ( n − 1 ) 2 \frac{n(n+1)(n-1)}{2} 2n(n+1)(n1)

然后考虑怎么求这个东西: ∑ 1 ≤ i &lt; j ≤ n L C P ( s u f i , s u f j ) \sum_{1\leq i &lt; j\leq n}LCP(suf_i,suf_j) 1i<jnLCP(sufi,sufj)

我们换一个顺序,求 ∑ 1 ≤ i &lt; j ≤ n L C P ( s u f s a i , s u f s a j ) \sum_{1\leq i &lt; j\leq n}LCP(suf_{sa_i},suf_{sa_j}) 1i<jnLCP(sufsai,sufsaj)

那么我们只需要一个单调队列维护一下当前后缀和前面的后缀的 L C P LCP LCP长度,以及每一段的个数和所有段的总和就行了。


代码:

#include<bits/stdc++.h>
using namespace std; 
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs int N=500005;
namespace SA{
	char s[N];
	int sa[N],rk[N],ht[N],len;
	int b[N],wb[N];
	bool t[N<<1];
	int sta[N],num[N],top;
	inline bool islms(int i,bool *cs t);
	template<class T>
	inline void induced_sort(T s,int len,int siz,int sigma,bool *cs t,int *cs cb,int *cs p);
	template<class T>
	inline void sais(T s,int len,bool *cs t,int *cs b1,int sigma);
	inline void init();
	inline void solve();
} 

inline bool SA::islms(int i,bool *cs t){
	return i>0&&t[i]&&!t[i-1];
}

template<class T>
inline void SA::induced_sort(T s,int len,int siz,int sigma,bool *cs t,int *cs cb,int *cs p){
	memset(b,0,sigma<<2);
	memset(sa,-1,len<<2);
	for(int re i=0;i<len;++i)++b[s[i]];
	cb[0]=b[0];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i];
	for(int re i=siz-1;~i;--i)sa[--cb[s[p[i]]]]=p[i];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i-1];
	for(int re i=0;i<len;++i)if(sa[i]>0&&!t[sa[i]-1])sa[cb[s[sa[i]-1]]++]=sa[i]-1;
	cb[0]=b[0];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i];
	for(int re i=len-1;~i;--i)if(sa[i]>0&&t[sa[i]-1])sa[--cb[s[sa[i]-1]]]=sa[i]-1;
}

template<class T>
inline void SA::sais(T s,int len,bool *cs t,int *cs b1,int sigma){
	int *cb=b+sigma,siz=0,cnt=0,p=-1;
	t[len-1]=1;
	for(int re i=len-2;~i;--i)t[i]=s[i]==s[i+1]?t[i+1]:(s[i]<s[i+1]);
	for(int re i=1;i<len;++i)if(islms(i,t))b1[siz++]=i;
	induced_sort(s,len,siz,sigma,t,cb,b1);
	for(int re i=siz=0;i<len;++i)if(islms(sa[i],t))sa[siz++]=sa[i];
	memset(sa+siz,-1,(len-siz)<<2);
	for(int re i=0;i<siz;++i){
		re int x=sa[i];
		for(int re j=0;j<len;++j){
			if(p==-1||s[x+j]!=s[p+j]||t[x+j]!=t[p+j]){
				++cnt;p=x;
				break;
			}
			else if(j>0&&(islms(x+j,t)||islms(p+j,t)))break;
		}
		sa[siz+(x>>1)]=cnt-1;
	}
	for(int re i=len-1,j=len-1;i>=siz;--i)if(~sa[i])sa[j--]=sa[i];
	int *s1=sa+len-siz,*b2=b1+siz;
	if(cnt<siz)sais(s1,siz,t+len,b1+siz,cnt);
	else for(int re i=0;i<siz;++i)sa[s1[i]]=i;
	for(int re i=0;i<siz;++i)b2[i]=b1[sa[i]];
	induced_sort(s,len,siz,sigma,t,cb,b2);
}

inline void SA::init(){
	len=fread(s+1,1,N-1,stdin);
	while(isspace(s[len]))--len;
	sais(s+1,len+1,t,wb,128);
	rk[0]=0,sa[0]=len+1;
	for(int re i=1;i<=len;++i)rk[++sa[i]]=i;
	for(int re i=1,k=0,j=0;i<len;ht[rk[i++]]=k)
	for(k?--k:0,j=sa[rk[i]-1];s[i+k]==s[j+k];++k);
}

inline void SA::solve(){
	re ll ans=((ll)(len-1)*(len+1)*len/2),sum=0;
	for(int re i=2;i<=len;++i){
		re int now=1;
		while(top&&sta[top]>=ht[i]){
			now+=num[top];
			sum-=(ll)num[top]*sta[top];
			--top;
		}
		num[++top]=now;
		sta[top]=ht[i];
		sum+=(ll)num[top]*sta[top];
		ans-=sum<<1;
	}
	printf("%lld",ans);
}

signed main(){
	SA::init();
	SA::solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值