bzoj4453 cys就是要拿英魂!(后缀数组+单调栈+二分)

Description

pps又开始dota视频直播了!一群每天被pps虐的蒟蒻决定学习pps的操作技术,他们把pps在这局放的技能记录了下来,每个技能用一个字符表示。经过研究,蒟蒻们发现字典序更大的连招威力更大。于是所有蒟蒻都想学习pps最强的连招。但是他们太弱了,不能学会整个视频里的连招,只能学会陈老师一段区间间内的连招,可是这个他们求不出,于是只好向你求助。为了蒟蒻们不再被pps虐(怎么可能),请你帮帮他们。简化题意:给你一个字符串,
每次询问你一段区间的字典序最大的子串。

Input

第一行是一个字符串S,表示pps放的技能
第二行一个正整数Q,表示询问个数
接下来Q行,每行两个正整数[l,r],表示询问区间[l,r]中的字典序最大的子串。

Output

Q行,每行一个正整数,表示该区间内字典序最大的子串的起始位置。

Sample Input

Lets_go_mod_p!
5
2 2
3 3
2 5
1 10
2 9

Sample Output

2
3
3
3
3

数据范围
1<=|S|<=100000
1<=Q<=100000
1<=l<=r<=|S|

分析:
单个字符串的子串问题,我们可以考虑后缀数组
一开始可能想的比较简单,每个询问,我们只要查找区间内rak最大的后缀即可
大概因为排名靠后的后缀大概比排名靠前的后缀优秀)

然而事实并不是这样的

      0   1   2   3   4   5
s:    b   a   a   b   b   b
rak:  3   1   2   6   5   4
ask[0,3]
最优解显然是: [0,3] b a a b
而不是rak最大的:[3,3] b   

那么每个后缀到底在什么情况上回产生贡献nei?
i < j ,当前区间为 [ l , r ]

  • r a n k [ i ] > r a n k [ j ] i 一定比 j

  • r a n k [ i ] < r a n k [ j ] l c p ( i , j ) >= r j + 1 i 一定比 j
    也就是说 l c p 的长度太长,所以从 i 开始保留的 l c p 反而多,所以 i j

  • r a n k [ i ] < r a n k [ j ] l c p ( i , j ) < r j + 1 j 一定比 i

综上所述,后缀 i 产生贡献的区间 [ i , j + l c p ( i , j ) 1 ] ,其中 j i 后面第一个大于 r a n k [ i ] 的位置
也就是说一个最优值影响的区间是连续的

对于询问的区间我们按照左端点从大到小排序

维护一个栈,栈中的每一个元素对应了一段区间。每次扫到一个左端点后,依次弹出栈顶元素,知道当前这个左端点不会影响到当前栈顶的这个区间了。
还有种情况是可能会影响到最后那个区间的一部分,这样需要在这个区间中二分一下这个区间从哪裂开。
时间复杂度是O(nlogn)O(nlogn)的。

然后可以维护一个栈,将后缀从后向前加入栈中,并且保证栈底的rak最大

栈中的每一个元素对应了一段区间,表示当前元素在区间 [ l , r ] 中贡献最大答案

每次扫到一个左端点(设为 L )后,依次弹出栈顶元素,直到当前这个左端点可以包含当前栈顶的这个区间
也就是说,栈中所有元素能够贡献最大答案的区间都在 L 之后
那么当前询问的答案就在栈中了

但是我们不知道询问区间的右端点落到了哪个区间,我们就二分一下,
找到右端点所在区间被 x 元素控制,当前询问的答案即为 x

tip

一开始我的LCP都是这样写的:

int LCP(int x,int y) {
    int r=y-x+1;
    r=log(r)/log(2);
    return min(mn[x][r],mn[y-(1<<r)+1][r]);
}

但是我们在求 [ x , y ] l c p 时,实际上求是 m i n ( h e i g h t [ x + 1 ] , h e i g h t [ y ] )
所以修改如下:

int LCP(int x,int y) {
    int r=log(y-x)/log(2);
    return min(mn[x+1][r],mn[y-(1<<r)+1][r]);
}

好久不写RMQ,一上来就写错:

for (int i=1;i<20;i++)
    for (int j=0;j<len;j++) {
        if (j+(1<<i)>len) break;
        mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
    }
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N=100010;
int sa[N],hei[N],rak[N],wx[N],wy[N],cc[N];
int n,m,mn[N][20],ans[N],top;
char s[N];
struct node{
    int l,r,num;
};
node Q[N],S[N];

int cmp0(const node &A,const node &B) {
    return A.l>B.l;                        //按照左端点从大到小排序 
}

int cmp(int *y,int a,int b,int k,int len) {
    int ra1=y[a];
    int rb1=y[b];
    int ra2=a+k>=len? -1:y[a+k];
    int rb2=b+k>=len? -1:y[b+k];
    return ra1==rb1&&ra2==rb2;
}

void make_sa(int len) {
    int i,j,k,m,p;
    int *x=wx,*y=wy;
    m=128;
    for (i=0;i<m;i++) cc[i]=0;
    for (i=0;i<len;i++) cc[x[i]=s[i]]++;
    for (i=1;i<m;i++) cc[i]+=cc[i-1];
    for (i=len-1;i>=0;i--) sa[--cc[x[i]]]=i;

    for (k=1;k<=len;k<<=1) {    //k=1
        p=0;
        for (i=len-k;i<len;i++) y[p++]=i;
        for (i=0;i<len;i++) if (sa[i]>=k) y[p++]=sa[i]-k;
        for (i=0;i<m;i++) cc[i]=0;
        for (i=0;i<len;i++) cc[x[y[i]]]++;
        for (i=1;i<m;i++) cc[i]+=cc[i-1];
        for (i=len-1;i>=0;i--) sa[--cc[x[y[i]]]]=y[i];
        swap(x,y);
        x[sa[0]]=0;
        p=1;
        for (int i=1;i<len;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],k,len)? p-1:p++;
        if (p>=len) break;
        m=p;
    }
}

void make_hei(int len) {
    for (int i=0;i<len;i++) rak[sa[i]]=i;
    hei[0]=0;
    int k=0;
    for (int i=0;i<len;i++) {
        if (!rak[i]) continue;
        int j=sa[rak[i]-1];
        if (k) k--;
        while (s[i+k]==s[j+k]&&i+k<len&&j+k<len) k++;
        hei[rak[i]]=k;
    }

    memset(mn,0x33,sizeof(mn));
    for (int i=0;i<len;i++) mn[i][0]=hei[i];
    for (int i=1;i<20;i++)
        for (int j=0;j<len;j++) {
            if (j+(1<<i)>len) break;
            mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);  //j+(1<<(i-1))
        }
}

int LCP(int x,int y) {
    if (x>y) swap(x,y);
    int r=log(y-x)/log(2);
    return min(mn[x+1][r],mn[y-(1<<r)+1][r]);
}

int pd(int x,int y,int z) {
    if (rak[x]>rak[y]) return 1;   //x更优 
    int len=LCP(rak[x],rak[y]);
    if (len<z-y+1) return 0;    //y更优 
    return 1;
}

int main()
{
    scanf("%s",s);
    n=strlen(s);
    make_sa(n); make_hei(n);
    scanf("%d",&m);
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].l--; Q[i].r--;
        Q[i].num=i;
    }
    sort(Q+1,Q+1+m,cmp0);

    top=1;
    S[0].l=n; S[1].num=S[1].l=S[1].r=n-1;    
    int j;
    for (j=1;Q[j].l==n-1;j++) ans[Q[j].num]=n;

    for (int i=n-2;i>=0&&j<=m;i--) {    //插入i 
        bool flag=0;
        int now=top,l,r,mid;
        while (top) {
            int l=pd(i,S[top].num,S[top].l);   
            int r=pd(i,S[top].num,S[top].r);
            if (l&&r) top--;     //i的贡献大于栈顶区间
            if (!l&&!r) break;
            if (l&&!r) {
                flag=1;
                break;
            }
        }
        if (flag) {
            now=l=S[top].l;
            r=S[top].r;
            while (l<r) {
                mid=(l+r)>>1;
                if (pd(i,S[top].num,mid)) now=max(now,mid),l=mid+1;
                else r=mid;
            }
            S[top].l=now+1;
            top++;
            S[top].r=now;
            S[top].l=S[top].num=i;
        }
        else {
            S[++top].num=i;
            S[top].l=i;
            S[top].r=S[top-1].l-1;
        }
        while (Q[j].l==i&&j<=m) {
            l=1;r=top;now=Q[j].r;    //二分右端点所在区间
            while (l<r) {
                mid=(l+r)>>1;
                if (now>=S[mid].l&&now<=S[mid].r) break;
                if (now<S[mid].l) l=mid+1;
                else r=mid;
            }
            ans[Q[j].num]=S[(l+r)>>1].num+1;
            ++j;
        }
    }
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);

    return 0;
}
发布了941 篇原创文章 · 获赞 192 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/wu_tongtong/article/details/79772838