• 【题解】[HEOI2016/TJOI2016]字符串


    [HEOI2016/TJOI2016]字符串

    ( ext{Solution:})

    记录一下这题获得的启发。

    • 最长公共前缀/后缀一类问题,具有可二分性。

    考虑二分一个答案,然后看如何检验。

    如果答案是 (mid,) 那么对应的串应该就是 (s[ccdots c+mid-1],) 我们发现:它是前缀 (s[1cdots c+mid-1]) 的后缀。

    • 通过前缀把子串转化成后缀

    假定我们找到了一个点,它代表的串包含了 (s[ccdots c+mid-1],) 如何查看是不是满足有一个 (s[acdots b]) 的子串是其前缀呢?

    我们发现:它是区间内的 所有子串,也就是说,只要出现过即可

    那么就自然想到用 endpos 来判断。

    考虑一下,当前答案是 (mid,) 如果要满足,其结束位置至少应该在 (a+mid-1.) 也就是说,我们需要查询这个点的 endpos 是否包含了区间 ([a+mid-1,b]) 中的一个点。

    这就需要线段树维护 endpos 的 trick 了。

    接下来思考如何定位节点。考虑我们的 parent 树实际上是一棵 前缀树 ,它满足:每一条叶节点到根的路径对应一个前缀,同时,每一个非叶子节点到根的路径都对应了某前缀的后缀。

    也就是说,我们定位到一个点后,向上跳父亲,实际就是在不断找后缀的过程

    那么我们前文所述,把它看成了前缀 (s[1cdots c+mid-1]) 的后缀,于是我们思考,如何定位一个前缀?

    这简单,从根开始依次匹配,匹配到的点对应的前缀就是答案。

    那怎么往上跳定位呢?考虑经典技巧:倍增

    考虑如何判断是不是跳到了对应节点:它一定满足,自身的 (lenge mid,) 其父亲的 (len<mid.)(len) 自下向上又具有单调性

    考虑倍增跳父亲即可。剩下的就是线段树上查询了。复杂度 (O(nlog^2 n).)

    #include<bits/stdc++.h>
    using namespace std;
    const int N=4e6+10;
    const int SN=2e5+10;
    int n,m;
    char s[SN];
    int sufpos[SN];
    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 SGT{
        int ls[N],rs[N],node;
        void change(int &x,const int &L,const int &R,const int &pos){
            if(!x)x=++node;
            if(L==R)return;
            int mid=(L+R)>>1;
            if(pos<=mid)change(ls[x],L,mid,pos);
            else change(rs[x],mid+1,R,pos);
        }
        int merge(const int &x,const int &y){
            if(!x||!y)return x|y;
            int p=++node;
            ls[p]=merge(ls[x],ls[y]);
            rs[p]=merge(rs[x],rs[y]);
            return p;
        }
        bool query(const int &x,const int &L,const int &R,const int &l,const int &r){
            if(!x)return 0;
            if(L>=l&&R<=r)return 1;
            int mid=(L+R)>>1;
            int res=0;
            if(l<=mid)res=query(ls[x],L,mid,l,r);
            if(mid<r)res|=query(rs[x],mid+1,R,l,r);
            return res;
        }
    }
    using namespace SGT;
    namespace SAM{
        int len[SN],ch[SN][26],pa[SN],rt[SN],last=1,tot=1;
        vector<int>G[N];
        int f[N][21];
        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;
                    memcpy(ch[nq],ch[q],sizeof ch[q]);
                    pa[nq]=pa[q];pa[q]=pa[np]=nq;
                    for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
                }
            }
        }
        void dfs(int x){
            for(auto v:G[x]){
                f[v][0]=x;
                for(int i=1;i<21;++i)f[v][i]=f[f[v][i-1]][i-1];
                dfs(v);
                rt[x]=merge(rt[x],rt[v]);
            }
        }
        void Build(){
            for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
            dfs(1);
        }
    }
    using namespace SAM;
    bool check(int mid,int a,int b,int c){
        //endpos in [a+mid-1,b]
        //Let us find the position of c.
        int posc=sufpos[c+mid-1];
        //the sequence is [c,c+mid-1]
        for(int i=20;~i;--i)if(len[f[posc][i]]>=mid)posc=f[posc][i];
        return query(rt[posc],1,n,a+mid-1,b);
    }
    int main(){
        scanf("%d%d",&n,&m);
        scanf("%s",s+1);
        for(int i=1;i<=n;++i)insert(s[i]-'a');
        for(int i=1,now=1;i<=n;++i){
            int v=s[i]-'a';
            now=ch[now][v];
            sufpos[i]=now;
            change(rt[now],1,n,i);
        }
        Build();
        while(m--){
            int a,b,c,d;
            scanf("%d%d%d%d",&a,&b,&c,&d);
            int len1=b-a+1;
            int len2=d-c+1;
            int l=1,r=Min(len1,len2);
            int Ans=-1;
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(mid,a,b,c))l=mid+1,Ans=mid;
                else r=mid-1;
            }
            printf("%d
    ",Ans);
        }
        return 0;
    }
    
  • 相关阅读:
    SQLSERVER Truncate使用注意事项
    SQLSERVER Truncate使用注意事项
    c#事件使用示例详解
    c#事件使用示例详解
    C#反射の反射泛型
    C#反射の反射泛型
    C#反射设置属性值和获取属性值
    1336:【例31】找树根和孩子
    1336:【例31】找树根和孩子
    1037:计算2的幂
  • 原文地址:https://www.cnblogs.com/h-lka/p/15173371.html
Copyright © 2020-2023  润新知