洛谷 P4022 [CTSC2012]熟悉的文章 / YbtOJ「字符串算法」第3章 后缀自动机 J. 相似子串 题解--zhengjun

题目大意

给定 n n n 个主串和 m m m 个询问串。

对于每个询问串,求出最大的 l l l,使得存在一种将当前询问串拆分成若干个长度 ≥ l \ge l l 且在任一主串中出现过的子串的方案。

思路

蒟蒻还是不会广义后缀自动机,所以我们就用 SA + st 表 + 二分 + 单调队列解决这道问题。

首先,可以用 SA + st 表求出每一个询问串的任意一位为左端点时,最大的在主串中出现过的子串的长度。

可以直接在 s a sa sa 数组上记录前后第一个是主串的后缀的位置,然后前后求 L C P LCP LCP max ⁡ \max max 即可。

然后可以发现,答案是单调的,于是考虑二分,假设当前二分到了 l l l

接着,如果当前询问串为 s s s,然后用 v i v_i vi 表示刚刚求出来的以 i i i 为左端点,最大的在主串中出现过的子串的长度,这样就可以 d p dp dp 了。

我们可以从后往前 d p dp dp,记 g i g_i gi 表示将 i i i 号后缀匹配,最少剩几个零星的点(其余的都是匹配掉的长度 ≥ l \ge l l 的子串),然后列出 d p dp dp 方程式。

g i = max ⁡ { max ⁡ k = i + l i + v i { g k } , max ⁡ k = i + 1 l e n ( s ) + 1 { g k + k − i } } g_i=\max\{\max\limits_{k=i+l}^{i+v_i}\{g_k\},\max\limits_{k=i+1}^{len(s)+1}\{g_k+k-i\}\} gi=max{k=i+lmaxi+vi{gk},k=i+1maxlen(s)+1{gk+ki}}

其实就是分两类讨论,一类是接上了,中间没有剩余,一类是接不上,全部归为零星的。

d p dp dp 方程式中的第二项很好算,只要每次都把 g k + k g_k+k gk+k 更新一下就好了,前一维的话,发现 i + v i i+v_i i+vi 一定是随着 i i i 的减小而减小的(如果 v i + i > v i + 1 + i + 1 v_i+i>v_{i+1}+i+1 vi+i>vi+1+i+1,那就意味着在 i + 1 i+1 i+1 的位置,也可以到达 v i + i v_{i}+i vi+i 的位置,那么就不会比 v i + i v_i+i vi+i 小了),于是就是一个滑动窗口,用单调队列即可。

然后注意一下二分边界。

代码

#include<bits/stdc++.h>
using namespace std;typedef long long ll;const int N=1.1e6+10,K=log2(N)+2;string a;
int n,q,m,k,s[N],rk[N],old[N<<1],sa[N],cnt[N],p[N],id[N],h[N],pos[N],pre[N],nex[N],f[K][N],lg[N],st[N],ed[N],val[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=1;i<=n;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;}
}
void insert(int i){st[i]=m+1;for(char c:a)s[++m]=c,pos[m]=i;ed[i]=m;s[++m]=++k;pos[m]=i;}
void init(){
	for(int i=1;i<=m;i++)pre[i]=pos[sa[i]]<=n?i:pre[i-1];for(int i=m;i>=1;i--)nex[i]=pos[sa[i]]<=n?i:nex[i+1];
	for(int i=1;i<=m;i++)f[0][i]=h[i];for(int i=2;i<=m;i++)lg[i]=lg[i>>1]+1;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]);}
void getval(int i){
	if(pre[rk[i]])val[i]=max(val[i],LCP(sa[pre[rk[i]]],i));if(nex[rk[i]])val[i]=max(val[i],LCP(sa[nex[rk[i]]],i));
}
int g[N],que[N],head,tail;
bool check(int t,int x){
	int len=ed[t]-st[t]+1,mn=len+1,ans=1e8;head=1;tail=0;g[len+1]=0;for(int i=len,j=ed[t];i>=1;i--,j--){
		if(i+x<=len+1){while(head<=tail&&g[i+x]<=g[que[tail]])tail--;que[++tail]=i+x;}
		while(head<=tail&&que[head]>i+val[j])head++;if(head<=tail)g[i]=g[que[head]];else g[i]=1e8;
		g[i]=min(g[i],mn-i);mn=min(mn,g[i]+i);ans=min(ans,g[i]+i-1);}return ans*10<=len;
}
void run(int i){int l=0,r=ed[i]-st[i]+2,mid;while(l+1<r)(check(i,mid=(l+r)>>1)?l:r)=mid;printf("%d\n",l);}
int main(){
	k=128;scanf("%d%d",&q,&n);for(int i=1;i<=n;i++)cin>>a,insert(i);for(int i=1;i<=q;i++)cin>>a,insert(i+n);
	getsa(m,k);init();for(int i=1;i<=q;i++)for(int j=st[i+n];j<=ed[i+n];j++)getval(j);for(int i=1;i<=q;i++)run(i+n);
}

有问题请指教,谢谢–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、付费专栏及课程。

余额充值