【NOI2017模拟3.25】历史行程

本文介绍了一种利用字符串匹配和后缀数组(SA)解决特定问题的方法,即在一个长字符串中高效地找到多个询问区间内的最长公共后缀长度。通过倒转字符串将问题转化为后缀的最长公共前缀问题,并使用双向链表和离线莫队算法进行优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出一个长度为n的字符串,m次询问前缀l~r中两两的最长后缀最长是多少。
n,m<=1e5

Solution

显然先倒过来,询问变成求后缀的lcp
SA处理一下就好了。
但是rank不是有序的,无法处理,怎么办呢?
可以离线莫队,维护一个set,每次插入或删除都可以log n解决。
但是复杂度过高会T怎么办呢?
我们每次插入就是要求某个点的前继和后继。
这个东西可以用双向链表来维护。
但是链表如何O(1)插入?
我们会发现,如果我们按照之前删除的倒序插入,那么插入的复杂度就是O(1)的。
于是我们处理出询问左端点所在块的左端点到询问右端点的链表。
这个可以通过把右端点从大到小排序来实现。
因为左端点就是在块内变化的,所以每次我们把左端点删到这个块的右端点,然后再加回去,这样链表是不会发生变化的。
于是我们就强行把维护前后继的log给去掉了=w=
具体实现看代码应该能懂吧。。。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5,M=400;
int sa[N],rank[N],height[N],w[N],x[N],y[N],mi[18],f[N][18],lg[N];
int n,m,l,r,tot,L[N],R[N],a[N],b[N],pre[N],suf[N],an[N],ans;
char st[N];
struct note{
    int l,r,id;
    friend bool operator < (note x,note y) {
        return b[x.l]<b[y.l]||b[x.l]==b[y.l]&&x.r>y.r;
    }
}ask[N];
void tsort() {
    memset(w,0,sizeof(w));int mx=0;
    fo(i,1,n) w[x[y[i]]]++,mx=max(mx,x[y[i]]);
    fo(i,1,mx) w[i]+=w[i-1];
    fd(i,n,1) sa[w[x[y[i]]]--]=y[i];
}
void get_sa() {
    fo(i,1,n) y[i]=i;tsort();
    for(int j=1;j<=n;j=j*2) {
        int k=0;fo(i,n-j+1,n) y[++k]=i;
        fo(i,1,n) if (sa[i]>j) y[++k]=sa[i]-j;
        tsort();
        fo(i,1,n) y[i]=x[i],x[i]=0;
        x[sa[1]]=k=1;
        fo(i,2,n) {
            if (y[sa[i]]!=y[sa[i-1]]||y[sa[i]+j]!=y[sa[i-1]+j]) k++;
            x[sa[i]]=k;
        }
    }
    fo(i,1,n) rank[sa[i]]=i;
}
void get_height() {
    int k=0;
    fo(i,1,n) {
        if (k) k--;
        int j=sa[rank[i]-1];
        while (j+k<=n&&i+k<=n&&st[j+k]==st[i+k]) k++;
        height[rank[i]]=k;
    }
}
void get_rmq() {
    mi[0]=1;int len=log(n)/log(2);
    fo(i,1,len) mi[i]=mi[i-1]*2;
    fo(i,1,n) f[i][0]=height[i],lg[i]=log(i)/log(2);
    fo(j,1,len)
        fo(i,1,n-mi[j]+1)
            f[i][j]=min(f[i][j-1],f[i+mi[j-1]][j-1]);
}
int lcp(int x,int y) {
    if (x<1||y>n||x==y) return 0;
    x++;int z=lg[y-x+1];
    return min(f[x][z],f[y-mi[z]+1][z]);
}
void prepare() {
    fo(i,1,n) suf[i]=i+1,pre[i]=i-1;
    suf[0]=1;pre[n+1]=n;
}
void ins(int x) {
    ans=max(ans,max(lcp(pre[x],x),lcp(x,suf[x])));
    suf[pre[x]]=x;pre[suf[x]]=x;
}
void del(int x) {
    pre[suf[x]]=pre[x];
    suf[pre[x]]=suf[x];
}
int main() {
    freopen("history.in","r",stdin);
    freopen("history.out","w",stdout);
    scanf("%d%d",&n,&m);
    scanf("%s",st+1);
    fo(i,1,n/2) swap(st[i],st[n-i+1]);
    fo(i,1,n) x[i]=st[i];
    get_sa();
    get_height();
    get_rmq();
    fo(i,1,n) b[i]=(i-1)/M+1;
    fo(i,1,m) {
        scanf("%d%d",&l,&r);
        swap(l,r);l=n-l+1,r=n-r+1;
        if (b[l]==b[r]) {
            fo(j,l,r) a[j-l+1]=rank[j];
            sort(a+1,a+r-l+1+1);
            ans=0;
            fo(j,2,r-l+1) ans=max(ans,lcp(a[j-1],a[j]));
            an[i]=ans;
            continue;
        }
        ask[++tot].id=i;
        ask[tot].l=l;ask[tot].r=r;
    }
    sort(ask+1,ask+tot+1);
    int cnt=(n-1)/M+((n-1)%M>0);
    fo(i,1,cnt) L[i]=R[i-1]+1,R[i]=min(L[i]+M-1,n);
    prepare();L[cnt+1]=n+1;
    int l=1,r=n;
    fo(i,1,tot) {
        if (b[ask[i].l]!=b[ask[i-1].l]) {
            prepare();r=n;
            while (L[l+1]<=ask[i].l) l++;
            fo(j,1,R[l]-1) del(rank[j]);
            fd(j,n,R[l]+1) del(rank[j]);
            ans=0;
            fo(j,R[l]+1,n) ins(rank[j]),a[j]=ans;
            fd(j,R[l]-1,L[l]) ins(rank[j]);
        }
        while (r>ask[i].r) del(rank[r]),r--;
        ans=a[r];
        fo(j,L[l],R[l]-1) del(rank[j]);
        fd(j,R[l]-1,ask[i].l) ins(rank[j]);
        an[ask[i].id]=ans;
        fd(j,ask[i].l-1,L[l]) ins(rank[j]);
    }
    fo(i,1,m) printf("%d\n",an[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值