冲刺NOI2017 (24) A (后缀数组 回滚莫队)

题目大意

给出一个长度为 n 的字符串s,提出 q 个询问,对于每个询问要求回答:右端点在区间[l,r]的所有前缀,最长公共后缀最长的一对前缀的最长公共后缀的长度是多少。


题解

因为要在原串的前缀上分析后缀十分的别扭,所以对这个问题略作转化:将原串翻转过来,求左端点在区间 [l,r] 内的后缀的lcp(最长公共前缀)。同时不要忘记把询问也翻转到同样的姿势。

问题已经转化为了:左端点在区间 [l,r] 的所有后缀,lcp最长的一对后缀的lcp长度。因为要求出后缀间的lcp,所以处理出后缀数组和高度数组。因为每对后缀的lcp就是两者在 sa 数组中的位置间 height 的最小值,所以对于一些后缀,他们间的最大lcp就是分布在 sa 数组上的若干个散点中,若干个点对的lcp的最大值。

只分析到这里的话,对于每个询问可以 O((rl+1)2) 地枚举后缀对,在已经预处理过 height 数组的 st 表的情况下,对于每一个后缀对可以 O(1) 地求出lcp并维护最大值。

但是这样的复杂度是非常不可观的,分析后缀间lcp的性质:若有三个左端点分别为 a,b,c 的后缀,且在 sa 数组上 rnk[a]<rnk[b]<rnk[c] ,若记 lcp(x,y) 为左端点分别在 x,y 的后缀的 lcp 的话,那么一定满足 lcp(a,b)lcp(a,c) lcp(b,c)lcp(a,c)

有了上面的性质,虽然无法直接优化上面的做法,但是将所有的询问离线后这个性质就显得很有用了。考虑将所有询问离线后莫队,对于每个添加操作,就相当于在原有的一堆散点中插入了一个散点,新插入的点两侧的点原有的答案就一定不比新点与两侧的答案优,这些过程用几个多重集进行即可。对于每个删除操作就相当于添加操作的逆操作。这样做的时间复杂度为 O(n1.5logn) ,莫队常数挺大的,所以除非你真的很wys请不要尝试卡常通过….

考虑如何去掉这个log。发现添加操作是无法去掉log的,那么我们就干脆去掉添加操作,用只带删除的回滚莫队来解决这个问题。考虑用删除操作时用双向链表维护,每删除一个位置就修改其前驱和后继元素的指针并更新答案。这样就可以去掉维护位置的log了,因为虽然修改的次数很多,但是对修改后的答案进行的查询只有q个,所以考虑用分块维护链表中相邻位置的两后缀的lcp的长度,这样就可以每次 O(1) 修改, O(n) 查询了。这样总时间复杂度就降到了 O(n1.5)

回滚莫队见这里


代码

#include <cstdio>
#include <iostream>
//#include <ctime>
#include <algorithm>
using namespace std;

inline int read() {
    register int val=0; char ch;
    while(~(ch=getchar()) && (ch<'0' || ch>'9')); val=ch-'0';
    while(~(ch=getchar()) && (ch>='0' && ch<='9')) val=(val<<1)+(val<<3)+ch-'0';
    return val;
}

const int maxn=int(1e5)+111;
int n,m;
char s[maxn];
int siz, num, bel[maxn];
int ans[maxn];

struct Query {
    int l,r,id;
    Query() {}
    Query(int a,int b,int c):l(a),r(b),id(c) {}
    bool operator < (const Query &b) const {
        return bel[l]==bel[b.l]?r>b.r:bel[l]<bel[b.l];
    }
}q[maxn];

void Read() {
    register int i;
    n=read(), m=read();
    scanf("%s",s);
    reverse(s,s+n);

    while(siz*siz<n) ++siz;
    for(i=1;i<=n;++i)
        bel[i]=(i-1)/siz+1;
    num=bel[n];

    int l,r;
    for(i=1;i<=m;++i) {
        l=read(), r=read();
        q[i]=Query(n-r+1,n-l+1,i);
    }
    sort(q+1,q+1+m);
    return;
}

int K;
int sa[maxn], rnk[maxn], tmp[maxn];
int h[maxn];
int st[maxn][20];

inline bool cmp(const int &a,const int &b) {
    if(rnk[a]!=rnk[b]) return rnk[a]<rnk[b];
    else {
        int x=a+K<n?rnk[a+K]:-1;
        int y=b+K<n?rnk[b+K]:-1;
        return x<y;
    }
}

void Init_sa() {
    register int i;
    for(i=0;i<=n;++i) {
        sa[i]=i;
        rnk[i]=i<n?s[i]:-1;
    }
    for(K=1;K<=n;K<<=1) {
        sort(sa,sa+1+n,cmp);
        tmp[sa[0]]=1;
        for(i=1;i<=n;++i)
            if(cmp(sa[i-1],sa[i])) tmp[sa[i]]=tmp[sa[i-1]]+1;
            else tmp[sa[i]]=tmp[sa[i-1]];
        for(i=0;i<=n;++i)
            rnk[i]=tmp[i];
    }
    return;
}

void Init_lcp(int *lcp) {
    register int i, j, h=0;
    for(i=0;i<=n;++i) {
        rnk[sa[i]]=i;
        lcp[i]=0;
    }
    lcp[sa[0]]=0;
    for(i=0;i<n;++i) {
        j=sa[rnk[i]-1];
        if(h>0) --h;
        while(i+h<n && j+h<n && s[i+h]==s[j+h]) ++h;
        lcp[rnk[j]]=h;
    }
    return;
}

#define bin(k) (1<<(k))
int lg2[maxn];
void Init_lg2() {
    register int i,j;
    for(i=1,j=0;i<maxn;i<<=1,++j) lg2[i]=j;
    for(i=3;i<maxn;++i) if(!lg2[i]) lg2[i]=lg2[i-1];
    return;
}

void Init_st() {
    register int i,j;
    for(i=1;i<=n;++i) 
        st[i][0]=h[i];
    int upp=lg2[n];
    for(i=1;i<=upp;++i)
    for(j=1;j+bin(i)-1<=n;++j)
        st[j][i]=min(st[j][i-1],st[j+bin(i-1)][i-1]);
    return;
}

inline int RMQ(const int &l,const int &r) {
    int lg=lg2[r-l+1];
    return min(st[l][lg],st[r-bin(lg)+1][lg]);
}

void Init() {
    Init_lg2();
    Init_sa();
    Init_lcp(h);
    Init_st();
    return;
}

bool used[maxn];
int pre[maxn], nex[maxn];
int cnt[maxn], bcnt[400];

int getmax() {
    register int i,j;
    for(i=num;!bcnt[i];--i);
    for(j=i*siz;!cnt[j];--j);
    return j;
}

void pre_calc(int id) {
    register int i;
    for(i=0;i<=n;++i) {
        used[i]=false;
        cnt[i]=bcnt[bel[i]]=pre[i]=nex[i]=0;
    }
    for(i=(id-1)*siz+1;i<=n;++i)
        used[rnk[i-1]]=true;

    int cur, last=0;
    for(i=1;i<=n;++i) if(used[i]) {
        pre[i]=last;
        nex[last]=i;
        if(last) ++cnt[cur=RMQ(last,i-1)], ++bcnt[bel[cur]];
        last=i;
    }
    nex[last]=n+1;
    return;
}

#define mp make_pair
pair<int,int> s1[maxn], s2[maxn], s3[maxn]; //pre, nex, cnt
int t1,t2,t3;

void Del(int id,int sign) {
    id=rnk[id-1];
    int cur, tot=0;
    if(pre[id]>=1) {
        cur=RMQ(pre[id],id-1); ++tot;
        if(sign) s3[++t3]=mp(cur,-1);
        --cnt[cur], --bcnt[bel[cur]];
        if(sign) s2[++t2]=mp(pre[id],nex[pre[id]]);
        nex[pre[id]]=nex[id];
    }
    if(nex[id]<=n) {
        cur=RMQ(id,nex[id]-1); ++tot;
        if(sign) s3[++t3]=mp(cur,-1);
        --cnt[cur], --bcnt[bel[cur]];
        if(sign) s1[++t1]=mp(nex[id],pre[nex[id]]);
        pre[nex[id]]=pre[id];
    }
    if(tot==2) {
        cur=RMQ(pre[id],nex[id]-1);
        if(sign) s3[++t3]=mp(cur,1);
        ++cnt[cur], ++bcnt[bel[cur]];
    }
    return;
}

void Rollback() {
    while(t1) {
        pre[s1[t1].first]=s1[t1].second;
        --t1;
    }
    while(t2) {
        nex[s2[t2].first]=s2[t2].second;
        --t2;
    }
    while(t3) {
        cnt[s3[t3].first]-=s3[t3].second;
        bcnt[bel[s3[t3].first]]-=s3[t3].second;
        --t3;
    }
    return;
}

int Mo(int pos,int id) {
    register int i=pos, ql=(id-1)*siz+1, qr=n;
    pre_calc(id);

    for(i=pos;bel[q[i].l]==id;++i) {
        while(qr>q[i].r) Del(qr--,0);
        while(ql<q[i].l) Del(ql++,1);
        ans[q[i].id]=getmax();
        Rollback(); ql=(id-1)*siz+1;
    }
    return i;
}

void Solve() {
    register int i,pos=1;
    for(i=1;i<=num;++i)
        pos=Mo(pos,i);
    return;
}

int main() {
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
//  double t1=clock();

    Read();
    Init();
    Solve();

    for(int i=1;i<=m;++i)
        printf("%d\n",ans[i]);

//  printf("%.3lfsec\n",(clock()-t1)/CLOCKS_PER_SEC);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值