后缀数组

后缀数组

原理介绍

排序:

\(s\) 为原串, \(s'_{i,j}\) 表示 \(s[i,\min (i+2^j,|s|)]\)\(rk_{i,j}\) 表示 \(s'_{i,j}\) 在集合 $ S_j={s'_{i,j}~~|~i\in [1,|s|]}$ 中的排名。

则有 \(rk_{i,j+1}\) 相当于以 \(rk_{i,j}\) 为第一关键字,以 \(rk_{i+2^j,j}\) 为第二关键字排序之后的排名。

排序直接用基数排序即可,时间复杂度 \(O(n\log n)\)

void RadixSort()//基数排序
{
	for(int i=0;i<=m;i++) tak[i]=0;//桶
	for(int i=1;i<=n;i++) tak[rk[i]]++;
	for(int i=1;i<=m;i++) tak[i]+=tak[i-1];
	for(int i=n;i;i--) sa[tak[rk[tp[i]]]--]=tp[i];//从大到小枚举第二关键字赋值
}

void build()
{
	for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
	RadixSort();
	for(int len=1;len<=n;len*=2)
	{
		for(int i=1;i<=len;i++) tp[i]=n-len+i;//第二关键字初始化,超出的部分赋成极小值
		int p=len;
		for(int i=1;i<=n;i++) 
			if(sa[i]>len) tp[++p]=sa[i]-len;//第二关键字
		RadixSort(),std::swap(tp,rk),p=0;//基数排序,保存rk数组
		for(int i=1;i<=n;i++)
		{
			if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+len]!=tp[sa[i-1]+len]) p++;
			rk[sa[i]]=p;//获取排名
		}m=p;//重定义值域
	}
}

求lcp:

\(sa_i\) 为字典序第 \(i\) 小的后缀下标,\(h_i\)\(\texttt{lcp} (s[sa_i,n],s[sa_{i+1},n])\)

则一定有 $\texttt{lcp}(s[i,n],s[j,n]) =\min_{p=rk_i}^{rk_{j-1}} h_p $。

证明:不妨设 \(rk_j<rk_i\) , 一定有 \(\texttt{lcp}(s[i,n],s[j,n]) \leq \texttt{lcp}(s[sa_{rk_i-1},n],s[j,n])\),直接归纳证明即可。

求h数组
for(int i=1,j=0;i<=n;i++)
{
	if(j) j--;
	while(s[i+j]==s[sa[rk[i]-1]+j]) j++;
	h[rk[i]]=j;
}
for(int i=1;i<=n;i++) st[i][0]=h[i]; //st表
for(int j=1;j<=19;j++)
	for(int i=1;i<=n-(1<<j)+1;i++) 
		st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
求lcp
int lcp(int l,int r)
{
	int x=rk[l],y=rk[r];
	if(x>y) swap(x,y); x++;
	int k=lg[y-x+1];
	return min(st[x][k],st[y-(1<<k)+1][k]);//区间最小值
}

例题[P9482 NOI2023字符串]

求lcp。

二进制分组

原理介绍

如果要求强制在线,那么可以把某个不能支持在线加入但是可以以 \(O(siz)\) 的复杂度合并的某个数据结构多建几个,每次如果存在两个相同大小的这个数据结构,那么将他们两个合并,时间复杂度类似于启发式合并,某个加入的数据结构只会被合并 \(O(\log n)\) 次,对总时间复杂度的贡献就是 \(O(\log n)\),查询的时候对于每个数据结构的答案累加计算即可,这样的合并策略可以发现一共同时也只有 \(O(\log n)\)个数据结构。

代码

void upd(const T &s,int x)
{
	rt[++cnt]=&tr[++tot];
	insert(s,rt[cnt],x);
	siz[cnt]=1;
	while(cnt>1&&siz[cnt]==siz[cnt-1])
	{
		rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
		siz[cnt-1]+=siz[cnt],cnt--;
	}
	return build(rt[cnt]);
}

例题[[CF710F](Problem - F - Codeforces)]:

AC自动机要求加串删串强制在线,删串直接贡献-1的权值即可。

node* merge(node *x,node *y)
{
	if(!x||!y) return x?x:y;
	x->ed+=y->ed;
	for(int i=0;i<26;i++) 
		x->son[i]=merge(x->son[i],y->son[i]);
	return x;
}

void upd(const string &s,int x)
{
	rt[++cnt]=&tr[++tot];
	insert(s,rt[cnt],x);
	siz[cnt]=1;
	while(cnt>1&&siz[cnt]==siz[cnt-1])
	{
		rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
		siz[cnt-1]+=siz[cnt],cnt--;
	}
	return build(rt[cnt]);
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	while(n--)
	{
		int op;string s;
		cin>>op>>s;
		if(op==1) upd(s,1);
		else if(op==2) upd(s,-1);
		else {
			ll res=0;
			for(int i=1;i<=cnt;i++) res+=query(s,rt[i]);
			cout<<res<<endl;
		}
	}
	return 0;
}
posted @ 2026-01-15 17:01  Sgt_Dante  阅读(3)  评论(0)    收藏  举报