[BZOJ1396]识别子串-后缀树

识别子串

Description

pic

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值