[BJWC2018]Border 的四种求法 题解

SAM+线段树合并+树链剖分

感谢 gls 的教导!!!

Statement

多次询问区间 Border 长度。

\(n,q\le 2\times 10^5\)

[BJWC2018]Border 的四种求法

Solution

每一个询问就是 \(\min\{i|lcp(l,i)\ge r-i+1,i\in(l,r]\}\) ,其中 \(lcp(i,j)\) 表示 \(suf[i]\)\(suf[j]\) 的最长公共前缀。

移一下项,\(lcp(l,i)+i\ge r+1\)

看到 \(lcp\) ,可以想到对原串的反串建立 SAM,提出来一棵 parent 树,那么 \(lcp(i,j)\) 便对应 \(n-i+1,n-j+1\) 在树上对应节点的 \(lca\)\(len\)

考虑暴力做法,每次从 \(n-l+1\) 对应节点一个一个点向树根跳,钦定当前跳到的点 \(x\) 就是 \(lca\)

每次即是询问 \(x\) 的子树中最小满足条件的点

一个问题,这里的子树应不应该去掉上一个跳到的点的子树呢

其实无所谓,显然上一个点的 \(len >\) 当前点的 \(len\),所以当前点算出来的答案可行那么在之前的点子树中肯定也可行

考虑如何询问最小的满足 \(lcp(l,i)+i\ge r+1\) 的子树中的 \(i\),容易想到线段树合并后线段树上二分。线段树节点 \([L,R]\) 记录当前子树中,\(lcp+i\) 最大值即可。

注意到每次在合并子树的时候,应将子树中的 \(lcp\) 都按当前点的 \(len\) 计算

暴力适用于原串随机,即 parent 树树高 log,于是考虑树链剖分

树剖后,容易发现每一次询问的其实都是一些树链的前缀

\(y\)\(x\) 向上跳重链时进入当前重链的点,则贡献来自两种地方:

  • \(y\) 的子树
  • \(y\) 往上直到重链顶的那些点,以及它们的轻子树

我们两种贡献分两次算,因为线段树合并顺序有点不同

第一种直接合并子树然后做就可以了

第二种,先合并掉轻子树,然后把自己并给重儿子,最后递归重儿子。这样做到了一个前缀的效果

容易想到可以把询问离线,挂到 \(log\) 条重链的某个点上,每次合并完信息处理一下即可

\(O((n+q)\log n)\)

Code

时刻小心自己是反串

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+5;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}

struct SAM{
    int ch[N][26],link[N],len[N];
    int las,tot;
    char s[N];

    SAM(){las=tot=1;}
    void insert(int x){
        int p=las;
        len[las=++tot]=len[p]+1;
        for(;p&&!ch[p][x];p=link[p])ch[p][x]=las;
        if(!p)return link[las]=1,void();
        int q=ch[p][x];
        if(len[q]==len[p]+1)
            return link[las]=q,void();
        len[++tot]=len[p]+1,link[tot]=link[q];
        memcpy(ch[tot],ch[q],sizeof(ch[tot]));
        for(;ch[p][x]==q;p=link[p])ch[p][x]=tot;
        link[las]=link[q]=tot;
    }
}sam;
struct Segment_Tree{
    #define ls t[rt].l
    #define rs t[rt].r
    #define mid ((l+r)>>1)
    struct Tree{
        int l,r,ori,mx,tg;
        void init(){l=r=ori=mx=0,tg=-1;}
    }t[N*80];
    int siz=0;

    void pushup(int rt){
        t[rt].mx=max(t[ls].mx,t[rs].mx);
        t[rt].ori=max(t[ls].ori,t[rs].ori);
    }
    void pushdown(int rt){
        if(t[rt].tg==-1)return ;
        if(ls)t[ls].mx=t[ls].ori+t[rt].tg,t[ls].tg=t[rt].tg;
        if(rs)t[rs].mx=t[rs].ori+t[rt].tg,t[rs].tg=t[rt].tg;
        t[rt].tg=-1;
    }
    void insert(int l,int r,int& rt,int id,int v){
        if(!rt)rt=++siz,t[rt].init();
        if(l==r)return t[rt].ori=l,t[rt].mx=l+v,void();
        pushdown(rt);
        if(id<=mid)insert(l,mid,ls,id,v);
        else insert(mid+1,r,rs,id,v);
        pushup(rt);
    }
    void change(int l,int r,int rt,int L,int R,int v){
        if(!rt)return ;
        if(L<=l&&r<=R){
            t[rt].tg=v,t[rt].mx=t[rt].ori+v;
            return ;
        }
        pushdown(rt);
        if(L<=mid)change(l,mid,ls,L,R,v);
        if(mid<R)change(mid+1,r,rs,L,R,v);
        pushup(rt);   
    }
    int query(int l,int r,int rt,int L,int R,int v){
        if(!rt||r<L||R<l)return 1e9;
        if(L<=l&&r<=R){
            if(l==r)return t[rt].mx>=v?l:1e9;
            pushdown(rt);
            if(t[ls].mx>=v)return query(l,mid,ls,L,R,v);
            if(t[rs].mx>=v)return query(mid+1,r,rs,L,R,v);
            return 1e9;
        }
        pushdown(rt);
        int tmp=query(l,mid,ls,L,R,v);
        if(tmp==1e9)return query(mid+1,r,rs,L,R,v);
        return tmp;
    }
    int merge(int l,int r,int p,int q){
        if(!p||!q)return p+q;
        pushdown(p),pushdown(q);
        t[p].l=merge(l,mid,t[p].l,t[q].l);
        t[p].r=merge(mid+1,r,t[p].r,t[q].r);
        return pushup(p),p;
    }
    #undef ls
    #undef rs
    #undef mid
}seg;
struct Query{int l,r,id;};
int f[N],siz[N],son[N],bot[N],dfn[N],rev[N],top[N];
int pos[N],rot[N],ans[N],l[N],r[N];
vector<Query>ques[N];
vector<int>Edge[N];
int n,q,tim;

void dfs1(int u){
    for(auto v:Edge[u])
        siz[v]=1,dfs1(v),siz[u]+=siz[v],
        (siz[v]>siz[son[u]]&&(son[u]=v,1));
}
void dfs2(int u,int tp){
    top[u]=tp,rev[dfn[u]=++tim]=u;
    if(son[u])
        dfs2(son[u],tp),bot[u]=bot[son[u]];
    else bot[u]=u;
    for(auto v:Edge[u])if(v^son[u])dfs2(v,v);
}
void calc1(int u){
    for(auto v:Edge[u])if(v^son[u]){
        calc1(v),seg.change(1,n,rot[bot[v]],1,n,sam.len[u]);
        rot[u]=seg.merge(1,n,rot[u],rot[bot[v]]);
    }
    for(auto v:ques[u])
        ans[v.id]=min(ans[v.id],seg.query(1,n,rot[u],v.l+1,v.r,v.r+1));
    if(son[u])rot[son[u]]=seg.merge(1,n,rot[son[u]],rot[u]),calc1(son[u]);
}
void calc2(int u){
    if(son[u])calc2(son[u]),rot[u]=seg.merge(1,n,rot[u],rot[son[u]]);
    for(auto v:Edge[u])if(v^son[u])
        calc2(v),rot[u]=seg.merge(1,n,rot[u],rot[v]);
    seg.change(1,n,rot[u],1,n,sam.len[u]);
    for(auto v:ques[u])
        ans[v.id]=min(ans[v.id],seg.query(1,n,rot[u],v.l+1,v.r,v.r+1));
}

signed main(){
    scanf("%s",sam.s+1),n=strlen(sam.s+1);
    reverse(sam.s+1,sam.s+1+n);
    for(int i=1;i<=n;++i)sam.insert(sam.s[i]-'a'),pos[i]=sam.las;
    for(int i=2;i<=sam.tot;++i)Edge[f[i]=sam.link[i]].push_back(i);
    dfs1(siz[1]=1),dfs2(1,1),q=read();
    for(int i=1,u;i<=q;++i){
        l[i]=read(),r[i]=read(),u=pos[n-l[i]+1],ans[i]=1e9;
        while(u)ques[u].push_back((Query){l[i],r[i],i}),u=f[top[u]];
    }
    for(int i=1;i<=n;++i)
        seg.insert(1,n,rot[pos[i]],n+1-i,sam.len[pos[i]]);
    calc1(1),memset(rot,0,sizeof(rot)),seg.siz=0;
    for(int i=1;i<=n;++i)
        seg.insert(1,n,rot[pos[i]],n+1-i,sam.len[pos[i]]);
    calc2(1);
    for(int i=1;i<=q;++i)
        printf("%d\n",max(0,r[i]+1-ans[i]));
    return 0;
}
posted @ 2022-04-01 17:19  _Famiglistimo  阅读(59)  评论(0编辑  收藏  举报