[HEOI2016/TJOI2016]字符串(后缀数组+二分+主席树/后缀自动机+倍增+线段树合并)

后缀数组解法:

先二分最长前缀长度 \(len\),然后从 \(rnk[c]\) 向左右二分 \(l\)\(r\) 使 \([l,r]\)\(height\geq len\),然后在主席树上查 \(sa[l..r]\) 是否有 \(a..b\) 中的任意一个数。时间复杂度 \(O(n\log^2 n)\)

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],f[maxn][18],g[maxn][18];
int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20],cnt;char s[maxn];

inline int read(){
	register int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return (f==1)?x:-x;
}

void SA(int m){
	int i,j,k,p,d=0;
	for(i=1;i<=n;i++) rnk[i]=s[i];
	for(i=1;i<=n;i++) tax[rnk[i]]++;
	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
	for(k=1,p=0;p<n;m=p,k<<=1){
		p=0;
		for(i=n-k+1;i<=n;i++) tp[++p]=i;
		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
		for(i=1;i<=m;i++) tax[i]=0;
		for(i=1;i<=n;i++) tax[rnk[i]]++;
		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
		swap(rnk,tp);rnk[sa[1]]=p=1;
		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
	}
	for(i=1;i<=n;i++) rnk[sa[i]]=i;
	for(i=1;i<=n;i++){
		p=sa[rnk[i]-1];if(d) d--;
		while(s[i+d]==s[p+d]) d++;
		h[rnk[i]]=d;
	} 
	for(i=1;i<=n;i++) f[i][0]=h[i+1],g[i][0]=h[i];
	for(j=1;j<=17;j++){
		for(i=1;i+(1<<j)-1<=n;i++) f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		for(i=(1<<j);i<=n;i++) g[i][j]=min(g[i][j-1],g[i-(1<<(j-1))][j-1]);
	}
}

void update(int &now,int pre,int l,int r,int x){
	now=++cnt;L[now]=L[pre];R[now]=R[pre];sum[now]=sum[pre]+1;
	if(l == r) return ;
	int mid=(l+r)>>1;
	if(x <= mid) update(L[now],L[pre],l,mid,x);
	else update(R[now],R[pre],mid+1,r,x);
}

int query(int u,int v,int Le,int Ri,int l,int r){
	if(Le <= l && r <= Ri) return sum[v]-sum[u];
	int mid=(l+r)>>1,ans=0;
	if(Le <= mid) ans+=query(L[u],L[v],Le,Ri,l,mid);
	if(Ri > mid) ans+=query(R[u],R[v],Le,Ri,mid+1,r);
	return ans;
}

int check(int len,int x,int a,int b){
	int l=x,r=x;
	for(int i=17;i>=0;i--)
		if(l-(1<<i)>=1&&g[l][i]>=len) l-=1<<i;
	for(int i=17;i>=0;i--)
		if(r+(1<<i)<=n&&f[r][i]>=len) r+=1<<i;
	int ans=query(T[l-1],T[r],a,b-len+1,1,n);
	return ans;
}

int main()
{
	n=read(),m=read();
	scanf("%s",s+1);SA(128);
	for(int i=1;i<=n;i++) update(T[i],T[i-1],1,n,sa[i]);
	int a,b,c,d,l,r,mid,ans=0;
	while(m--){
		a=read(),b=read(),c=read(),d=read();
		l=1;r=min(b-a+1,d-c+1);ans=0;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(mid,rnk[c],a,b)) l=mid+1,ans=mid;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

后缀自动机解法:

因为后缀自动机只能处理后缀,所以我们将原串翻转一下,将最长前缀长度转化为最长后缀长度。然后我们对原串建一个后缀自动机,二分一下最长后缀长度 \(len\),从 \(pos[c]\) 出发倍增到 \(l[x]\geq len\) 的结点 \(x\),然后在 \(x\) 上查询 \(endpos\) 集合是否有 \(b+len-1..a\) 的任意一个数。维护 \(endpos\) 集合可以用线段树合并。

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
int n,m,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn];
int pos[maxn],f[maxn][21],T[maxn],L[maxn*60],R[maxn*60],sum[maxn*60],tot;
char s[maxn];

inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}

void insert(int c){
    int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    if(!p) fa[q]=1;
    else {
        int r=ch[p][c];
        if(l[p]+1==l[r]) fa[q]=r;
        else {
            int s=++cnt;l[s]=l[p]+1;
            memcpy(ch[s],ch[r],sizeof(ch[r]));
            fa[s]=fa[r];fa[r]=fa[q]=s;
            for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
        }
    }
}

void pushup(int now){
    sum[now]=sum[L[now]]+sum[R[now]];
}

void update(int &now,int l,int r,int x){
    if(!now) now=++tot;
    if(l == r){sum[now]++;return ;}
    int mid=(l+r)>>1;
    if(x <= mid) update(L[now],l,mid,x);
    else update(R[now],mid+1,r,x);
    pushup(now);
}

int merge(int x,int y,int l,int r){
    if(!x||!y) return x+y;
    if(l == r){sum[x]+=sum[y];return x;}
    int mid=(l+r)>>1,z=++tot;
    L[z]=merge(L[x],L[y],l,mid);
    R[z]=merge(R[x],R[y],mid+1,r);
    pushup(z);
    return z;
}

int query(int now,int Le,int Ri,int l,int r){
    if(!now) return 0;
    if(Le <= l && r <= Ri) return sum[now];
    int mid=(l+r)>>1,ans=0;
    if(Le <= mid) ans+=query(L[now],Le,Ri,l,mid);
    if(Ri > mid) ans+=query(R[now],Le,Ri,mid+1,r);
    return ans;
}

int check(int len,int x,int L,int R){
    for(int i=20;i>=0;i--)
        if(f[x][i]&&l[f[x][i]]>=len) x=f[x][i];
    return query(T[x],L+len-1,R,1,n);
}

int main()
{
    n=read(),m=read();
    scanf("%s",s+1);last=cnt=1;
    reverse(s+1,s+n+1);
    for(int i=1;i<=n;i++){
        insert(s[i]-'a');pos[i]=last;
        update(T[pos[i]],1,n,i);
    }
    for(int i=1;i<=cnt;i++) b[l[i]]++;
    for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
    for(int i=1;i<=cnt;i++) a[b[l[i]]--]=i;
    for(int i=cnt;i>=1;i--)
        if(fa[a[i]]) T[fa[a[i]]]=merge(T[fa[a[i]]],T[a[i]],1,n);
    for(int i=1;i<=cnt;i++) f[i][0]=fa[i];
    for(int j=1;j<=20;j++)
        for(int i=1;i<=cnt;i++) f[i][j]=f[f[i][j-1]][j-1];
    int a,b,c,d,l,r,mid,ans;
    while(m--){
        a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
        l=0,r=min(a-b+1,c-d+1),ans=0;
        while(l<=r){
            mid=(l+r)>>1;
            if(check(mid,pos[c],b,a)) l=mid+1,ans=mid;
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2019-01-18 18:15  Owen_codeisking  阅读(318)  评论(0编辑  收藏  举报