#1762. 识别子串(string)
题意
内存限制:128 MiB
时间限制:1000 ms
$L \leq 100000$
题解
考虑建出parent树,只有叶子结点上所接受的串在原串中出现一次,而其最长接受的串为原串的一段前缀
所以对于其叶子结点 $i$ 上在原串中对应着 $[1,len_i]$~$[len_i-len_{fa_i},len_i]$
所以对于 $[1,len_i-len_{fa_i}]$ 的点,可以把 $len_i$ 作为其右端点,对于 $[len_i-len_{fa_i}+1,len_i]$ 的点,可以把 $len_{fa_i}+1$ 作为包含它的区间长度
所以开两棵线段树,维护最小右端点和最小长度即可
#include <bits/stdc++.h> using namespace std; const int N=2e5+5; int n,lst=1,sz=1,ans[N]; char s[N];bool g[N]; struct SAM{ int link,len; map<int,int>nx; }a[N]; void build(int x){ int np=++sz,p=lst; a[np].len=a[p].len+1; while(p && !a[p].nx.count(x)) a[p].nx[x]=np,p=a[p].link; if (!p) a[np].link=1; else{ int q=a[p].nx[x]; if (a[q].len==a[p].len+1) a[np].link=q; else{ int nq=++sz; a[nq].len=a[p].len+1; a[nq].link=a[q].link; a[nq].nx=a[q].nx; a[q].link=a[np].link=nq; while(p && a[p].nx[x]==q) a[p].nx[x]=nq,p=a[p].link; } } lst=np; } struct T{ #define Ls k<<1 #define Rs k<<1|1 #define mid ((l+r)>>1) int in[N*2]; void build(int k,int l,int r){ in[k]=1e9;if (l==r) return; build(Ls,l,mid);build(Rs,mid+1,r); } void update(int k,int l,int r,int L,int R,int v){ if (L>R) return; if (L<=l && r<=R){in[k]=min(in[k],v);return;} if (mid>=L) update(Ls,l,mid,L,R,v); if (mid<R) update(Rs,mid+1,r,L,R,v); } int query(int k,int l,int r,int x,int v){ if (l==r) return min(v,in[k]); if (mid>=x) return query(Ls,l,mid,x,min(v,in[k])); return query(Rs,mid+1,r,x,min(v,in[k])); } }t[2]; int main(){ scanf("%s",s+1);n=strlen(s+1); for (int i=1;i<=n;i++) build(s[i]-'a'); for (int i=1;i<=sz;i++) g[a[i].link]=1; t[0].build(1,1,n);t[1].build(1,1,n); for (int l,r,i=1;i<=sz;i++) if (!g[i]) r=l=a[i].len,l-=a[a[i].link].len, t[0].update(1,1,n,1,l-1,r), t[1].update(1,1,n,l,r,r-l+1); for (int i=1;i<=n;i++) printf("%d\n",min(t[0].query(1,1,n,i,1e9)-i+1,t[1].query(1,1,n,i,1e9))); return 0; }