【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\n",l);
    }

    return 0;
}
posted @ 2018-05-14 16:51  Maxwei_wzj  阅读(90)  评论(0编辑  收藏  举报