【BZOJ1396】识别子串-后缀自动机+线段树

测试地址:识别子串
做法:本题需要用到后缀自动机+线段树。
很快能有一个想法,就是用后缀自动机求出所有识别子串,然后在线段树上区间更新。可是识别子串的数目可能很多,直接更新会挂,这是不是就意味着不能做呢?别急,先来考虑识别子串这个性质会不会使它的分布更加特殊。
很快我们便观察到了一个事实,包含一个识别子串的所有子串都是识别子串。我们知道子串的集合就等同于所有前缀的所有后缀的集合,根据上面的性质,我们可以试图找到一个前缀最短的识别子串后缀。
先来考虑在后缀自动机上什么样的串才是识别子串,很显然,它在后缀树的子树中应该只包含一个前缀节点,所以我们通过DFS对每个点计算出这个前缀节点,这个前缀节点影响到的深度最小的点中,在后缀自动机上可以识别出的最短的串就是我们所求的最短识别子串后缀。这样的最短识别子串后缀显然只有O(n)个。
所以现在我们可以来考虑更新答案。假设我们求出的最短后缀是(l,r),它和以它为后缀的所有子串对(1,r)这些位置的贡献是:
对于(1,l1)中的点i,影响是ri+1,这些贡献在i增长时是一条斜率为1的线段,显然可以对所有这些线段维护一棵线段树来进行区间更新。
对于(l,r)中的所有点,影响是rl+1,这个就更加显然可以用线段树维护了。
最后在线段树中下推标记到每个叶子节点就可以求出每个点的答案了,时间复杂度为O(nlogn)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,tot=1,last=1;
int ch[200010][26]={0},pre[200010],len[200010];
int vis[200010]={0};
int seg0[400010],seg1[400010];
char s[100010];
struct edge
{
    int v,next;
}e[200010];
int first[200010]={0},tote=0;

void extend(int c)
{
    int p,q,np,nq;
    p=last;
    np=++tot;
    len[np]=len[last]+1;
    while(p&&!ch[p][c]) ch[p][c]=np,p=pre[p];
    if (p)
    {
        q=ch[p][c];
        if (len[p]+1==len[q])
            pre[np]=q;
        else
        {
            nq=++tot;
            for(int i=0;i<26;i++)
                ch[nq][i]=ch[q][i];
            pre[nq]=pre[q],len[nq]=len[p]+1;
            pre[q]=pre[np]=nq;
            while(p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
        }
    }
    else pre[np]=1;
    last=np;
}

void build()
{
    pre[1]=len[1]=0;
    for(int i=0;i<n;i++)
    {
        extend(s[i]-'a');
        vis[last]=i+1;
    }
}

void buildtree(int no,int l,int r)
{
    seg0[no]=seg1[no]=inf;
    if (l==r) return;
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
}

void pushdown(int no,int l,int r)
{
    int mid=(l+r)>>1;
    seg0[no<<1]=min(seg0[no<<1],seg0[no]);
    seg0[no<<1|1]=min(seg0[no<<1|1],seg0[no]+l-mid-1);
    seg1[no<<1]=min(seg1[no<<1],seg1[no]);
    seg1[no<<1|1]=min(seg1[no<<1|1],seg1[no]);
    seg0[no]=seg1[no]=inf;
}

void modify(int no,int l,int r,int s,int t,int d,bool type)
{
    if (l>=s&&r<=t)
    {
        if (!type) seg0[no]=min(seg0[no],s-l+d);
        else seg1[no]=min(seg1[no],d);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no,l,r);
    if (s<=mid) modify(no<<1,l,mid,s,t,d,type);
    if (t>mid) modify(no<<1|1,mid+1,r,s,t,d,type);
}

void insert(int a,int b)
{
    e[++tote].v=b;
    e[tote].next=first[a];
    first[a]=tote;
}

void dfs(int v)
{
    for(int i=first[v];i;i=e[i].next)
    {
        dfs(e[i].v);
        if (vis[v]==0) vis[v]=vis[e[i].v];
        else vis[v]=-1;
    }
    for(int i=first[v];i;i=e[i].next)
        if (vis[v]!=vis[e[i].v])
        {
            int x=len[pre[e[i].v]]+1;
            int p=vis[e[i].v]-x;
            if (p>0) modify(1,0,n-1,0,p-1,x+p,0);
            modify(1,0,n-1,p,x+p-1,x,1);
        }
}

void work()
{
    buildtree(1,0,n-1);
    for(int i=2;i<=tot;i++)
        insert(pre[i],i);
    dfs(1);
}

void solve(int no,int l,int r)
{
    if (l==r)
    {
        printf("%d\n",min(seg0[no],seg1[no]));
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no,l,r);
    solve(no<<1,l,mid);
    solve(no<<1|1,mid+1,r);
}

int main()
{
    scanf("%s",s);
    n=strlen(s);

    build();
    work();
    solve(1,0,n-1);

    return 0;
}
posted @ 2018-08-09 12:12  Maxwei_wzj  阅读(152)  评论(0编辑  收藏  举报