YbtOJ「字符串算法」第3章 后缀自动机 I. 独特子串 题解--zhengjun

题目大意

给定 \(n\) 个字符串,求出每个字符串只属于该字符串的本质不同的非空子串的个数。

思路

如果没有做过这道 SA 入门题《不同子串个数》,那么请先了解这道题的 SA 做法。

首先老套路,把所有字符串拼接在一起。

然后单独考虑一个字符串 \(i\),首先求出这个字符串的每一个后缀有多少个前缀没有在其他字符串中出现过,假设当前在求排名为 \(i\) 的后缀的值,那么就需要知道排名在 \(i\) 前面的第一个后缀 \(sa_j\) 与后缀 \(sa_i\) 不在同一个字符串里(即求出最大的 \(pre_i=j\),使得 \(j\le i,id_{sa_j}\ne id_{sa_i}\)\(id\) 表示第 \(i\) 号位置的字符是哪个字符串的),然后求出最小的 \(nex_i=j\),使得 \(j\ge i,id_{sa_j}\ne id_{sa_i}\),这一步可以线性递推,这样,这个后缀的在其他字符串中出现过的前缀个数就是 \(\max\{LCP(i,sa_{pre_i}),LCP(i,sa_{nex_i}\}\)

接着,只需要向刚刚的那道题一样处理,减去每个后缀与排名在它前一个的后缀的公共前缀减掉就好了。

代码

#include<bits/stdc++.h>
using namespace std;typedef long long ll;const int N=2e5+10,K=log2(N)+2;string a;ll ans;
int n,m,k,s[N],rk[N],sa[N],old[N<<1],cnt[N],p[N],id[N],h[N],st[N],ed[N],pos[N],t[N],lg[N],f[K][N],pre[N],nex[N];
void getsa(int n,int m){
	for(int i=1;i<=n;i++)cnt[rk[i]=s[i]]++;for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;for(int i=1;i<=m;i++)cnt[i]=0;for(int len=1,k;len==1||m^n;m=k,len<<=1){
		k=0;for(int i=n-len+1;i<=n;i++)p[++k]=i;for(int i=1;i<=n;i++)if(sa[i]>len)p[++k]=sa[i]-len;
		for(int i=1;i<=n;i++)cnt[id[i]=rk[p[i]]]++;for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--)sa[cnt[id[i]]--]=p[i];for(int i=1;i<=m;i++)cnt[i]=0;for(int i=1;i<=n;i++)old[i]=rk[i];
		k=0;for(int i=1;i<=n;i++)rk[sa[i]]=old[sa[i]]==old[sa[i-1]]&&old[sa[i]+len]==old[sa[i-1]+len]?k:++k;
	}for(int i=1,k=0;i<=n;i++){if(k)k--;while(max(i,sa[rk[i]-1])+k<=n&&s[i+k]==s[sa[rk[i]-1]+k])k++;h[rk[i]]=k;}h[1]=0;
}
void init(){
	for(int i=2;i<=m;i++)lg[i]=lg[i>>1]+1;for(int i=1;i<=m;i++)f[0][i]=h[i];
	for(int i=1;(1<<i)<=m;i++)for(int j=1;j+(1<<i)-1<=m;j++)f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
int LCP(int l,int r){l=rk[l];r=rk[r];if(l>r)swap(l,r);int k=lg[r-l++];return min(f[k][l],f[k][r-(1<<k)+1]);}
int main(){
	k=128;scanf("%d",&n);for(int i=1;i<=n;i++){cin>>a;st[i]=m+1;for(char c:a)s[++m]=c,pos[m]=i;s[++m]=++k;pos[m]=i;ed[i]=m;}
	getsa(m,k);init();for(int i=1;i<=m;i++)if(pos[sa[i]]==pos[sa[i-1]])pre[i]=pre[i-1];else pre[i]=i-1;
	for(int i=m;i>=1;i--)if(pos[sa[i]]==pos[sa[i+1]])nex[i]=nex[i+1];else nex[i]=i+1;for(int i=1;i<=n;i++){
		ans=1ll*(ed[i]-st[i]+1)*(ed[i]-st[i])/2;for(int j=st[i];j<=ed[i];j++)if(pre[rk[j]])t[j]=LCP(j,sa[pre[rk[j]]]);
		for(int j=st[i];j<=ed[i];j++)if(nex[rk[j]])t[j]=max(t[j],LCP(j,sa[nex[rk[j]]]));for(int j=st[i];j<ed[i];j++)
			if(!(pos[sa[rk[j]-1]]^i))t[j]=max(t[j],h[rk[j]]);for(int j=st[i];j<=ed[i];j++)ans-=t[j];printf("%lld\n",ans);
	}return 0;
}

如有问题请指出,谢谢--zhengjun

posted @ 2022-06-11 15:41  A_zjzj  阅读(21)  评论(0编辑  收藏  举报