zoukankan      html  css  js  c++  java
  • 【BZOJ4556】字符串(TJOI&HEOI2016)-后缀数组+二分+RMQ+主席树

    测试地址:字符串
    做法:本题需要用到后缀数组+二分+RMQ+主席树。
    注意到要求s(a,b)的每个子串和s(c,d)的LCP最大值,其实就是求s(a,b)的每个后缀和s(c,d)的LCP最大值。要求LCP我们通常要先对字符串求出后缀数组,然后在height上做RMQ,可以做到O(1)询问。我们知道s(a,b)的一个后缀s(x,b)s(c,d)的LCP长度为:min(LCP(x,c),bx+1),其中LCP(a,b)指以第a个字符开头的后缀和以第b个字符开头的后缀的LCP。这个东西和x的取值有关,所以我们不好直接求,怎么办呢?
    观察到一个性质,如果长为x的LCP存在,那么长为x1的LCP显然也存在,这个性质是单调的,因此我们二分答案mid,问题转化为判定性问题:存不存在长为mid的LCP?那么首先,s(a,b)的一个后缀s(x,b)要满足bx+1midx的取值范围应该是区间[a,bmid+1],所以问题就变成,求以第a个到第bmid+1个字符开头的这些后缀中,存不存在一个后缀与以第c个字符开头的后缀的LCP长度mid
    我们又发现,与第c个字符开头的后缀的LCP长度mid的那些后缀,在后缀数组上是一个连续的区间,且我们可以二分找到这个区间,二分的复杂度是O(logn)的,二分时要求两个后缀的LCP,我们前面已经说了能用RMQ做到O(1)询问,所以可以接受。
    于是问题就变成给定一个序列(即rank),问其中的某个区间中存不存在一个数在某一个权值区间内。这是非常经典的主席树的应用,于是我们在主席树上就可以做到O(logn)查询。那么我们就解决了这一题,总的时间复杂度为O(nlog2n)
    (本来以为这题这么复杂要调很久,没想到1A了,信心up)
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,tot=0,rt[100010],ch[2000010][2]={0},sum[2000010]={0};
    int x[100010],y[100010],cnt[100010],s[100010];
    int SA[100010],Rank[100010],height[100010];
    int mn[100010][20],len[100010];
    char S[100010];
    
    void calc_SA()
    {
        int p=1,m=26;
        for(int i=1;i<=n;i++)
            x[i]=S[i]-'a'+1;
        while(p<n)
        {
            for(int i=1;i<=n;i++)
            {
                if (i+p<=n) y[i]=x[i+p];
                else y[i]=0;
            }
    
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;i++) cnt[y[i]]++;
            for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
            for(int i=1;i<=n;i++) s[cnt[y[i]]--]=i;
    
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;i++) cnt[x[i]]++;
            for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
            for(int i=n;i>=1;i--) SA[cnt[x[s[i]]]--]=s[i];
    
            m=0;
            for(int i=1;i<=n;i++)
            {
                if (i==1||x[SA[i]]!=x[SA[i-1]]||y[SA[i]]!=y[SA[i-1]])
                    m++;
                Rank[SA[i]]=m;
            }
            if (m==n) break;
            for(int i=1;i<=n;i++)
                x[i]=Rank[i];
    
            p<<=1;
        }
    }
    
    void calc_height()
    {
        int last=0;
        for(int i=1;i<=n;i++)
        {
            if (Rank[i]==n)
            {
                height[Rank[i]]=last=0;
                continue;
            }
            while(S[i+last]==S[SA[Rank[i]+1]+last]) last++;
            height[Rank[i]]=last;
            last=max(last-1,0);
        }
    }
    
    void pre_rmq()
    {
        int x=0;
        for(int i=1;i<n;i++)
        {
            if ((1<<(x+1))<i) x++;
            len[i]=x;
        }
        for(int i=1;i<n;i++)
            mn[i][0]=height[i];
        for(int i=1;i<=17;i++)
            for(int j=1;j+(1<<(i-1))<n;j++)
                mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
    }
    
    int LCP(int l,int r)
    {
        r--;
        if (l>r) return l;
        int p=len[r-l+1];
        return min(mn[l][p],mn[r-(1<<p)+1][p]);
    }
    
    void buildtree(int &v,int l,int r)
    {
        v=++tot;
        if (l==r) return;
        int mid=(l+r)>>1;
        buildtree(ch[v][0],l,mid);
        buildtree(ch[v][1],mid+1,r);
    }
    
    void insert(int &v,int last,int l,int r,int x)
    {
        v=++tot;
        sum[v]=sum[last];
        ch[v][0]=ch[last][0];
        ch[v][1]=ch[last][1];
        if (l==r) {sum[v]++;return;}
        int mid=(l+r)>>1;
        if (x<=mid) insert(ch[v][0],ch[last][0],l,mid,x);
        else insert(ch[v][1],ch[last][1],mid+1,r,x);
        sum[v]=sum[ch[v][0]]+sum[ch[v][1]];
    }
    
    bool query(int last,int v,int l,int r,int s,int t)
    {
        if (sum[v]-sum[last]==0) return 0;
        if (l>=s&&r<=t) return 1;
        int mid=(l+r)>>1;
        if (s<=mid&&query(ch[last][0],ch[v][0],l,mid,s,t)) return 1;
        if (t>mid&&query(ch[last][1],ch[v][1],mid+1,r,s,t)) return 1;
        return 0;
    }
    
    bool check(int x,int a,int b,int c)
    {
        int l,r,L,R;
    
        c=Rank[c];
        l=1,r=c;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (LCP(mid,c)>=x) r=mid;
            else l=mid+1;
        }
        L=l;
    
        l=c,r=n;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (LCP(c,mid+1)>=x) l=mid+1;
            else r=mid; 
        }
        R=l;
    
        return query(rt[a-1],rt[b-x+1],1,n,L,R);
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        scanf("%s",S+1);
        calc_SA();
        calc_height();
        pre_rmq();
        buildtree(rt[0],1,n);
        for(int i=1;i<=n;i++)
            insert(rt[i],rt[i-1],1,n,Rank[i]);
    
        for(int i=1;i<=m;i++)
        {
            int a,b,c,d;
            scanf("%d%d%d%d",&a,&b,&c,&d);
            int l=0,r=min(b-a+1,d-c+1);
            while(l<r)
            {
                int mid=(l+r)>>1;
                if (check(mid+1,a,b,c)) l=mid+1;
                else r=mid;
            }
            printf("%d
    ",l);
        }
    
        return 0;
    }
  • 相关阅读:
    mysql workbench 建表时PK, NN, UQ, BIN, UN, ZF, AI
    Asan检测内存读越界
    C 实现 C++ 的面向对象特性(封装、继承、多态)
    VIBE算法
    Go 大坑 nil
    求二叉树中节点的最大距离
    计算[1,N]范围内含有7的数字的个数
    一组便于创建线程和线程池的简单封装函数
    用C#执行doc命令
    可以自由停靠的窗体!
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793381.html
Copyright © 2011-2022 走看看