zoukankan      html  css  js  c++  java
  • 【题解】[BJOI2020] 封印

    [BJOI2020] 封印

    ( ext{Solution:})

    求一个字符串一段区间与 (t) 的最长公共子串。

    考虑 SAM 与 AC 自动机相似的性质,按照惯例我们先对 (t) 建立 SAM ,在上面跑出对 (s) 的每个前缀,其能与 (t) 匹配的,以 (s_i) 结尾的最长公共子串长度 (mt_i)

    关于匹配的过程就直接在 SAM 上面跳就好了。注意如果跳到根还没有对应出边的话匹配长度就是 (0.)

    那么知道了 (mt_i,) 答案就被转化为:

    [max_{i=l}^r min{i-l+1,mt_i} ]

    最小值最大……二分。

    观察,如果满足 (min)(mt_i,) 那么必然有:

    [i-l+1ge mt_i \ i-mt_i+1ge l ]

    那么,不等式的右侧是一个定值,观察左侧:(i) 每次会单增 (1,) 但是注意,(mt_i) 的最大增长就是 (+1,) 其余要么不变要么变小。

    所以, (i-mt_i+1) 是一个不降的序列!

    那么必然存在一个位置 (pos) 满足 (forall ige pos,min{i-l+1,mt_i}=mt_i,forall i<pos,min{i-l+1,mt_i}=i-l+1)

    (pos) 显然是满足 (i-mt_i+1ge l) 的最小的 (pos.) 又因为那东西不降,直接二分即可。

    最后的答案就是 (max{pos-l,max_{i=pos}^r mt_i},) 后面的 (max) 可以用 st表 做到 (O(1).)

    总时间复杂度就是 (O(nlog n).)

    一个细节:之前看某篇博客上面讲的是对 (S) 建立 SAM 求出 (T) 在上面每个前缀匹配到的后缀长度。由于笔者脑子不好使 一时间没有发觉这是错的,实际上这样匹配出来的没有对应询问的区间限制,显然是错误的。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=4e5+10;
    char s[N],t[N],q;
    int slen,tlen,mt[N],f[N][20];
    inline int Min(int x,int y){return x<y?x:y;}
    inline int Max(int x,int y){return x>y?x:y;}
    namespace SAM{
        int len[N],pa[N],ch[N][26],last=1,tot=1;
        void insert(const int &c){
            int p=last;
            int np=++tot;
            last=tot;
            len[np]=len[p]+1;
            for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
            if(!p)pa[np]=1;
            else{
                int q=ch[p][c];
                if(len[q]==len[p]+1)pa[np]=q;
                else{
                    int nq=++tot;
                    len[nq]=len[p]+1;
                    pa[nq]=pa[q];pa[q]=pa[np]=nq;
                    memcpy(ch[nq],ch[q],sizeof ch[q]);
                    for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
                }
            }
        }
        void Do(){
            int now=1;
            for(int i=1;i<=tlen;++i){
                int v=t[i]-'a';
                if(ch[now][v])mt[i]=mt[i-1]+1,now=ch[now][v];
                else{
                    while(now&&!ch[now][v])now=pa[now];
                    if(!now)now=1;
                    else mt[i]=len[now]+1,now=ch[now][v];
                }
          }
        }
    }
    int lg[N],qq;
    int query(int l,int r){
        if(l>r)return 0;
        int k=lg[r-l+1];
        return Max(f[l][k],f[r-(1<<k)+1][k]);
    }
    int main(){
        scanf("%s",s+1);
        scanf("%s",t+1);
        swap(s,t);
        slen=strlen(s+1);
        tlen=strlen(t+1);
        for(int i=1;i<=slen;++i)SAM::insert(s[i]-'a');
        // puts("SAM?");
        SAM::Do();
        // puts("mate len:");
        // for(int i=1;i<=tlen;++i)printf("%d ",mt[i]);
        // puts("");
        // puts("MATE?");
        
        lg[0]=-1;
        for(int i=1;i<=tlen;++i)lg[i]=lg[i>>1]+1,f[i][0]=mt[i];
        for(int i=1;(1<<i)<=tlen;++i){
            for(int j=1;j+(1<<i)-1<=tlen;++j){
                f[j][i]=Max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
            }
        }
        // puts("ST?");
        scanf("%d",&qq);
        while(qq--){
            int ql,qr;
            scanf("%d%d",&ql,&qr);
            int pos=qr+1;
            int R=qr,L=ql;
            while(ql<=qr){
                int mid=(ql+qr)>>1;
                if(mid-mt[mid]+1>=L)qr=mid-1,pos=mid;
                else ql=mid+1;
            }
            printf("%d
    ",Max(query(pos,R),pos-L));
        }
        return 0;
    }
    
  • 相关阅读:
    HDU--2546 饭卡(01背包)
    UVA--562 Dividing coins(01背包)
    UVA--624 CD(01背包+路径输出)
    PKU--3628 Bookshelf 2(01背包)
    ExecutorService介绍2
    ExecutorService介绍
    mac下设置命令别名
    如何在sourcetree 下提交代码到gerrit上
    vim下如何删除某行之后的所有行
    VMware网络设置的三种方式
  • 原文地址:https://www.cnblogs.com/h-lka/p/15170374.html
Copyright © 2011-2022 走看看