题意
∀i∈[1,n]∀i∈[1,n]求包含ii并且在原串中只出现一次的子串的最短长度
题解
因为每个点parentparent树上的父亲肯定是当前节点所代表的串的后缀
那么只出现一次的串就是parentparent树上的叶子节点
考虑这个点所代表的len[i]−len[fa[i]]len[i]−len[fa[i]]个子串
令L=len[i]−len[fa[i]],R=len[i]L=len[i]−len[fa[i]],R=len[i]
1.∀i∈[L,R],ans[i]=min{len[fa[i]]+1}=min{R−L+1}1.∀i∈[L,R],ans[i]=min{len[fa[i]]+1}=min{R−L+1}
因为父亲节点所代表的串都在叶子里出现过了,所以最短没出现的子串长度就是父亲的长度加11
这个子串出现次数也是11
那么
上面的东西涉及到区间修改和区间求最小值,用两颗线段树维护一下就好了
#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
while(z[++Z]=x%10+48,x/=10);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,M=2*N,inf=1061109567;
typedef long long ll;
typedef int arr[M];
int n,k;char s[N];string str[N];
struct seg{
int tr[N<<2];
#define lc p<<1
#define rc p<<1|1
seg(){memset(tr,63,sizeof tr);}
inline void cov(int p,int w){cmin(tr[p],w);}
inline void down(int p){if(tr[p]^inf)cov(lc,tr[p]),cov(rc,tr[p]),tr[p]=inf;}
void mdy(int p,int L,int R,int a,int b,int w){
if(a>b)return;
if(a<=L&&R<=b)return cov(p,w);
down(p);int mid=(L+R)>>1;
if(a<=mid)mdy(lc,L,mid,a,b,w);
if(b>mid)mdy(rc,mid+1,R,a,b,w);
}
int qry(int p,int L,int R,int a){
if(L==R)return tr[p];
down(p);int mid=(L+R)>>1;
if(a<=mid)return qry(lc,L,mid,a);
return qry(rc,mid+1,R,a);
}
#undef lc
#undef rc
}t1,t2;
struct SAM{
int las,T,ch[M][26];arr fa,len;
SAM(){las=T=1;}
inline void ins(int c){
int p=las,np;fa[las=np=++T]=1,len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
if(p){
int q=ch[p][c],nq;
if(len[p]+1==len[q])fa[np]=q;
else{
fa[nq=++T]=fa[q],len[nq]=len[p]+1,memcpy(ch[nq],ch[q],4*26);
for(fa[q]=fa[np]=nq;ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
}
}arr fg;
inline void sol(int n){
fp(i,1,T)fg[fa[i]]=1;
fp(i,1,T)if(!fg[i]){
int l=len[i]-len[fa[i]],r=len[i];
t1.mdy(1,1,n,1,l-1,r+1);
t2.mdy(1,1,n,l,r,r-l+1);
}
fp(i,1,n)we(min(t1.qry(1,1,n,i)-i,t2.qry(1,1,n,i)));
}
}p;
int main(){
#ifndef ONLINE_JUDGE
file("s");
#endif
scanf("%s",s+1);
fp(i,1,strlen(s+1))p.ins(s[i]-'a');
p.sol(strlen(s+1));
return Ot(),0;
}

本文介绍如何利用后缀自动机(SAM)求解在原字符串中仅出现一次的子串及其最短长度问题。文章详细解释了通过SAM树找到只出现一次的子串的方法,并给出完整的C++代码实现。
353

被折叠的 条评论
为什么被折叠?



