[后缀自动机]识别子串

题目描述

现在同学们把大多数作业都做完了,但是却被最后一个题给难住了。
一般地,对于一个字符串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

分析

首先看题是容易想到后缀自动机或后缀数组的
由于本蒟蒻太蒻不会打数组,于是用了自动机
然后我们的len是求最长路,显然我们要的不是这个,于是新建一个min储存最短路,min的转移显然
然后搞一个right集合,不用很复杂
接着用线段树维护自动机的min值,显然每次改变的区间为[right-min+1,right],改变值为min
最后把这个转到ans数组里,由于min值是向后扩展,所以对ansi求min(ansi,ansi+1 +1)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <memory.h>
#define rep(i,a,b) for (i=a;i<=b;i++)
const int N=100001;
using namespace std;
struct Edge {
    int u,v,nx;
}g[4*N];
int cnt,list[2*N];
struct Node {
    int len,link,sz,min,right;
    int c[26];
}s[2*N];
int sz,last;
struct Line {
    int l,r,p,d;
}a[4*N];
int lnt,rt;
string ss;
int len;
int ans[N];

void Addedge(int u,int v) {
    g[++cnt].u=u;g[cnt].v=v;g[cnt].nx=list[u];list[u]=cnt;
}

void Extend(char c,int i) {
    int cur=++sz,p;
    s[cur].min=s[cur].len=s[last].len+1;
    for (p=last;p!=-1&&!s[p].c[c-'a'];p=s[p].link) {
        s[p].c[c-'a']=cur;
        s[cur].min=min(s[cur].min,s[p].min+1);
    }
    last=cur;s[cur].right=i;s[cur].sz=1;
    if (p==-1) {
        s[cur].link=0; 
        return;
    }
    int q=s[p].c[c-'a'];
    if (s[q].len==s[p].len+1) {
        s[cur].link=q;
        return;
    }
    int clone=++sz;
    s[clone]=s[q];
    s[clone].len=s[p].len+1;
    s[q].min=s[p].len+2;
    s[cur].link=s[q].link=clone;
    for (;p!=-1&&s[p].c[c-'a']==q;p=s[p].link) s[p].c[c-'a']=clone;
}

void Get_size(int u) {
    for (int i=list[u];i;i=g[i].nx) {
        Get_size(g[i].v);
        s[u].sz+=s[g[i].v].sz;
    }
}

void Build(int &x,int l,int r) {
    if (!x) x=++lnt;
    if (l==r) {
        a[x].d=len;
        return;
    }
    int mid=l+r>>1;
    Build(a[x].l,l,mid);
    Build(a[x].r,mid+1,r);
    a[x].d=min(a[a[x].l].d,a[a[x].r].d);
}

void Mark(int x,int y) {
    if (!x) return;
    if (a[x].d>y) a[x].d=a[x].p=y;
}

void Update(int x) {
    if (a[x].p) {
        Mark(a[x].l,a[x].p);
        Mark(a[x].r,a[x].p);
        a[x].p=0;
    }
}

void Change(int x,int l,int r,int ll,int rr,int y) {
    if (ll<=l&&r<=rr) {
        Mark(x,y);
        return;
    }
    Update(x);
    int mid=l+r>>1;
    if (ll<=mid) Change(a[x].l,l,mid,ll,rr,y);
    if (mid<rr) Change(a[x].r,mid+1,r,ll,rr,y);
}

void Pushdown(int x,int l,int r) {
    if (l==r) {
        ans[l]=a[x].d;
        return;
    }
    Update(x);
    int mid=l+r>>1;
    Pushdown(a[x].l,l,mid);
    Pushdown(a[x].r,mid+1,r);
}

int main() {
    int i;
    cin>>ss;
    len=ss.length();
    s[0].link=-1;
    rep(i,0,len-1) Extend(ss[i],i+1);
    rep(i,1,sz) Addedge(s[i].link,i);
    Get_size(0);
    Build(rt,1,len);
    rep(i,1,sz)
    if (s[i].right&&s[i].sz==1) Change(rt,1,len,s[i].right-s[i].min+1,s[i].right,s[i].min);
    Pushdown(rt,1,len);
    for (i=len-1;i;i--) ans[i]=min(ans[i],ans[i+1]+1);
    rep(i,1,len) printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值