【GDOI2016模拟】识别子串

Description

现在同学们把大多数作业都做完了,但是却被最后一个题给难住了。
一般地,对于一个字符串S,和S中第k个字符,定义子串T=S(i..j)为一个关于k的识别子串,当且仅当
1、i<=k<=j。
2、T在S中只出现一次。
比如,对于banana的第5个字符,“nana”,“anan”,“anana”,“nan”,“banan”和“banana”都是关于它的识别子串。
自然,识别子串越短越好(太长了也就失去意义了),现在请你计算出对于一个字符串S,关于S的每一位的最短识别子串分别有多长。

Input

一行,一个长度为L的字符串S,S只包含小写字母。

Output

L行,每行1个整数,第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

Data Constraint

第一个点 L=100
第二个点 L=1000
第三个点 L=5000
第四个点到第十个点 L=100000

Solution

很容易看出是一道后缀自动机或者后缀数组的题。
我用的是后缀自动机:
在每次加入点处理的时候我们要处理出每个节点表示的状态到达起始点的最短长度。
这是表示当前节点在原串中能够取到的最短的子串。
处理出来我们暂且叫它mi[x],于是我们可以很自然地处理出每个节点状态出现的次数。
我们枚举一个节点表示的状态,如果这个状态出现的次数等于1,那么我们就可以使用线段树在当前节点在原串中的唯一right值p,在p-mi[x]+1~p上用mi[x]来取min。最后从后往前扫一遍再取min,详见代码。

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 200010
using namespace std;
char s[maxn];
int f[4*maxn],tag[4*maxn],len[maxn],las,sz,tot,n;
int last[maxn],next[maxn],tov[maxn],mi[maxn],ans[maxn];
int pre[maxn],son[maxn][27],freq[maxn],num[maxn];
void insert(int x,int y)
{
    tov[++tot]=y;
    next[tot]=last[x];
    last[x]=tot;
}
void pick(int v,int z)
{
    if(!v) return;
    if(f[v]>z) f[v]=tag[v]=z;
}
void update(int x)
{
    if(tag[x])
    {
        pick(x*2,tag[x]);
        pick(x*2+1,tag[x]);
        tag[x]=0;
    } 
}
void build(int l,int r,int v)
{
    if(l==r){f[v]=n;return;}
    int mid=(l+r)/2;
    build(l,mid,v*2),build(mid+1,r,v*2+1);
    f[v]=min(f[v*2],f[v*2+1]);
}
void change(int l,int r,int v,int x,int y,int z)
{
    if(l==x&&r==y)
    {
        pick(v,z);
        return;
    }
    update(v);
    int mid=(l+r)/2;
    if(y<=mid) change(l,mid,v*2,x,y,z);
    else if(x>mid) change(mid+1,r,v*2+1,x,y,z);
    else change(l,mid,v*2,x,mid,z),change(mid+1,r,v*2+1,mid+1,y,z);
}
void find1(int l,int r,int v)
{
    if(l==r){ans[l]=f[v];return;}
    update(v);
    int mid=(l+r)/2;
    find1(l,mid,v*2);
    find1(mid+1,r,v*2+1);
}
void add(int i,int x)
{
    int now=++sz,p=las;mi[now]=len[now]=len[las]+1;
    for (;p&&!son[p][x];p=pre[p]) 
        son[p][x]=now,mi[now]=min(mi[now],mi[p]+1);
    if(p)
    {
        int q=son[p][x];
        if(len[p]+1==len[q]) pre[now]=q;
        else
        {
            int np=++sz;len[np]=len[p]+1;
            memcpy(son[np],son[q],sizeof(son[q]));
            mi[np]=mi[q],mi[q]=len[p]+2,num[np]=num[q];
            for(;son[p][x]==q;p=pre[p]) son[p][x]=np;
            pre[np]=pre[q],pre[q]=pre[now]=np;
        }
    }
    else pre[now]=1;
    num[now]=i,freq[now]=1;
    las=now;
}
void dfs(int x)
{
    for (int i=last[x];i;i=next[i]) 
        dfs(tov[i]),freq[x]+=freq[tov[i]];
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);int i,j;sz=las=1;
    for (i=1;i<=n;++i) 
        add(i,s[i]-96); 
    for (i=2;i<=sz;++i) 
        insert(pre[i],i);
    dfs(1);
    build(1,n,1);
    for (i=2;i<=sz;++i) 
        if(num[i]&&freq[i]==1) change(1,n,1,num[i]-mi[i]+1,num[i],mi[i]);
    find1(1,n,1);
    for (i=n-1;i>=1;--i) ans[i]=min(ans[i],ans[i+1]+1);
    for (i=1;i<=n;++i) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值