识别子串
Description
Input
一行,一个由小写字母组成的字符串S,长度不超过10^5
Output
L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长.
Sample Input
agoodcookcooksgoodfood
Sample Output
1
2
3
3
2
2
3
3
2
2
3
3
2
1
2
3
3
2
1
2
3
4
HINT
蒟蒻不会用后缀自动机做这题怎么办……
思路:
从后缀树的角度还是十分好想的。
考虑后缀树上每个叶子结点,可以发现,对于一个叶子结点父亲边上的每个字符,一定有其根节点到当前字符位置的字符串,是当前字符的一个识别子串。
简单的说,就是从根节点到这条边上的任意一个字符的路径为对应字符的一个识别子串。
证明也是很好想的,既然这条边独一无二且只被经过一遍,那么它显然只出现过一次并合法。
对于这个叶子结点到根节点路径上除了父亲边以外的字符,这个叶子节点贡献的最短识别子串,为从根节点开始直到父亲边上的第一个字符处所代表的字符串。
原因也很简单,这是这个叶子节点给它到根节点路径上的所有节点所能贡献的最短的合法识别子串。
这样只要一遍dfs找到所有叶子结点并统计贡献即可~
考虑到每个节点会收到两种贡献,分别是以某个位置为起点到当前位置所组成的字符串,以及定长的字符串两种。
于是用两棵线段树维护一下即可,前者维护起点位置最小值,后者维护长度最小值即可~
另外Ukkeon构造后缀树巨难写,考试时不实用,于是脑补了一个后缀自动机转后缀树的方法~
(然而这个方法相对而言更慢一些)
#include<bits/stdc++.h>
using namespace std;
const int N=200009;
const int M=28;
const int Inf=1e9+7;
inline void chkmin(int &a,int b){if(a>b)a=b;}
inline void chkmax(int &a,int b){if(a<b)a=b;}
inline int minn(int a,int b){if(a<b)return a;return b;}
int n;
char str[N];
namespace sam
{
int ch[N][M],fa[N],len[N],tot,u;
int buc[N],id[N];
inline void init(){u=tot=1;}
inline void add(int v)
{
int now=++tot;
len[now]=len[u]+1;
while(u && !ch[u][v])
ch[u][v]=now,u=fa[u];
if(!u)
fa[now]=1;
else
{
int q=ch[u][v];
if(len[q]==len[u]+1)
fa[now]=q;
else
{
int newq=++tot;
memcpy(ch[newq],ch[q],sizeof(ch[q]));
len[newq]=len[u]+1;
fa[newq]=fa[q];
fa[q]=fa[now]=newq;
while(u && ch[u][v]==q)
ch[u][v]=newq,u=fa[u];
}
}
u=now;
}
inline void work()
{
for(int i=1;i<=tot;i++)
buc[len[i]]++;
for(int i=1;i<=n+1;i++)
buc[i]+=buc[i-1];
for(int i=tot;i>=1;i--)
id[buc[len[i]]--]=i;
}
}
namespace suffix_tree
{
int ch[N][M],l[N],r[N],dis[N];
inline int len(int x){return minn(r[x],n+1)-l[x];}
inline void build()
{
sam::work();
for(int i=1;i<=sam::tot;i++)
r[i]=Inf;
for(int i=sam::tot;i>=1;i--)
{
int u=sam::id[i];
if(u==1){l[u]=r[u]=-1;continue;}
l[u]=minn(r[u],n+1)-(sam::len[u]-sam::len[sam::fa[u]]);
chkmin(r[sam::fa[u]],l[u]);
ch[sam::fa[u]][str[l[u]]-'a']=u;
}
}
}
using namespace suffix_tree;
namespace segtree
{
int tv[N<<2],tp[N<<2];
inline void push(int x)
{
if(tv[x]!=Inf)
{
chkmin(tv[x<<1],tv[x]);
chkmin(tv[x<<1|1],tv[x]);
tv[x]=Inf;
}
if(tp[x]!=-Inf)
{
chkmax(tp[x<<1],tp[x]);
chkmax(tp[x<<1|1],tp[x]);
tp[x]=-Inf;
}
}
inline void build(int x,int l,int r)
{
tv[x]=Inf;tp[x]=-Inf;
if(l==r)return;
int mid=l+r>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
inline void modify(int x,int l,int r,int dl,int dr,int *t,int v)
{
if(l!=r)push(x);int mid=l+r>>1;
if(l==dl && r==dr)
{
if(t==tp)chkmax(t[x],v);
else chkmin(t[x],v);
return;
}
if(dr<=mid)modify(x<<1,l,mid,dl,dr,t,v);
else if(mid<dl)modify(x<<1|1,mid+1,r,dl,dr,t,v);
else modify(x<<1,l,mid,dl,mid,t,v),modify(x<<1|1,mid+1,r,mid+1,dr,t,v);
}
inline int query(int x,int l,int r,int p,int *t)
{
if(l==r)return t[x];
push(x);int mid=l+r>>1;
if(p<=mid)return query(x<<1,l,mid,p,t);
else return query(x<<1|1,mid+1,r,p,t);
}
}
using namespace segtree;
inline void dfs(int u,int fa)
{
for(int i=0;i<27;i++)
if(ch[u][i])
{
dis[ch[u][i]]=dis[u]+len(ch[u][i]);
dfs(ch[u][i],u);
}
if(n<=l[u] || r[u]!=Inf)return;
if(l[u]+2<=n)
modify(1,1,n,l[u]+2,n,tp,n-dis[u]+2);
if(n-dis[u]+2<=l[u]+1)
modify(1,1,n,n-dis[u]+2,l[u]+1,tv,dis[fa]+1);
}
int main()
{
sam::init();
scanf("%s",str);
n=strlen(str);
str[n]='a'+26;
sam::add(26);
for(int i=n-1;i>=0;i--)
sam::add(str[i]-'a');
build();
build(1,1,n);
dfs(1,0);
for(int i=1;i<=n;i++)
printf("%d\n",minn(query(1,1,n,i,tv),i-query(1,1,n,i,tp)+1));
return 0;
}