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

题目大意

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

思路

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

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

然后单独考虑一个字符串 i i i,首先求出这个字符串的每一个后缀有多少个前缀没有在其他字符串中出现过,假设当前在求排名为 i i i 的后缀的值,那么就需要知道排名在 i i i 前面的第一个后缀 s a j sa_j saj 与后缀 s a i sa_i sai 不在同一个字符串里(即求出最大的 p r e i = j pre_i=j prei=j,使得 j ≤ i , i d s a j ≠ i d s a i j\le i,id_{sa_j}\ne id_{sa_i} ji,idsaj=idsai i d id id 表示第 i i i 号位置的字符是哪个字符串的),然后求出最小的 n e x i = j nex_i=j nexi=j,使得 j ≥ i , i d s a j ≠ i d s a i j\ge i,id_{sa_j}\ne id_{sa_i} ji,idsaj=idsai,这一步可以线性递推,这样,这个后缀的在其他字符串中出现过的前缀个数就是 max ⁡ { L C P ( i , s a p r e i ) , L C P ( i , s a n e x i } \max\{LCP(i,sa_{pre_i}),LCP(i,sa_{nex_i}\} max{LCP(i,saprei),LCP(i,sanexi}

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

代码

#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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_zjzj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值