题解 TJOI/HEOI2016 字符串

题解 TJOI/HEOI2016 字符串

题面

loj

解析

二分答案 \(mid\).

考虑后缀数组求出的数组,那么只要看 \(sa\) 数组里的

包含 \(c\) 开头的后缀的

一段满足最长公共前缀 \(\geq mid\) 的区间里,有没有开头在 \([a,b-mid+1]\) 中的后缀即可.

(可能上面那段话太模糊,请继续看下去)

具体来说,先求出一个 \(l\),满足 \(sa\) 数组中 \([l,c]\) 这段区间的最长公共前缀都 \(\geq mid\).

再求 \(r\),满足 \(sa\) 数组中 \(c,r\) 这段区间的最长公共前缀 \(\geq mid\).

那么如果有开头

\(sa\) 数组中的 \([l,r]\) 范围内,

且在原串中 \([a,b-mid+1]\)

的后缀,

则答案 \(\geq mid\).

那么 \(l,r\) 可以在求出 \(height\) 数组的 \(\texttt{st}\) 表后,二分答案求.

而判断 \([l,r]\) 中是否有开头在 \([a,b-mid+1]\) 中的点的后缀,

可以以 \(rk\) 作为下标,建主席树,

每次查询 \([a,b-mid+1]\) 中的下标在 \([l,r]\) 中的个数,如果 \(>0\) 则说明存在.

这题需要对后缀数组,主席树的灵活运用,并需要一些做题技巧(二分答案).

code

#include <iostream>
#include <cstring>
#include <cstdio>
#define ll long long
using namespace std;

inline int read(){
	int sum=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
	while(c<='9'&&c>='0'){sum=sum*10+c-'0';c=getchar();}
	return f*sum;
}

const int N=200005;
int n,Q;
char ss[N];

namespace SA{
	int a[N],b[N],ca[N],cb[N],st[N];
	int sa[N],rk[N],ht[N];
	int lg[N],s[N<<1][18];//s_table
	inline void init(){
		int m=n;
		for(int i=1;i<=n;i++) ca[a[i]=ss[i]-'a'+1]++;
		for(int i=1;i<=m;i++) ca[i]+=ca[i-1];
		for(int i=n;i>=1;i--) sa[ca[a[i]]--]=i;
		rk[sa[1]]=1;
		for(int i=2;i<=n;i++)
			rk[sa[i]]=rk[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]]);
		for(int l=1;rk[sa[n]]<n;l<<=1){
			for(int i=0;i<=m;i++) ca[i]=cb[i]=0;
			for(int i=1;i<=n;i++){
				ca[a[i]=rk[i]]++;
				cb[b[i]=(i+l<=n? rk[i+l]:0)]++;
			}
			for(int i=1;i<=m;i++) ca[i]+=ca[i-1],cb[i]+=cb[i-1];
			for(int i=1;i<=n;i++) st[cb[b[i]]--]=i;
			for(int i=n;i>=1;i--) sa[ca[a[st[i]]]--]=st[i];
			rk[sa[1]]=1;
			for(int i=2;i<=n;i++)
				rk[sa[i]]=rk[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]]||b[sa[i]]!=b[sa[i-1]]);
		}
		int k=0;
		for(int i=1;i<=n;i++){
			if(rk[i]==1) continue;
			if(k) k--;
			int j=sa[rk[i]-1];
			while(max(i,j)+k<=n&&ss[i+k]==ss[j+k]) k++;
			ht[rk[i]]=k;
	 	}
		for(int i=1;i<=n;i++) s[i][0]=ht[i];
		for(int j=1;j<18;j++)
			for(int i=1;i<=n;i++)
				if(i+(1<<(j-1))<=n) s[i][j]=min(s[i][j-1],s[i+(1<<(j-1))][j-1]);
		lg[0]=-1;
		for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
	}
	inline int lcp(int l,int r){
		if(l>r) return 0x3f3f3f3f;
		int k=lg[r-l+1];
		return min(s[l][k],s[r-(1<<k)+1][k]);
	}
};
using namespace SA;

namespace tr{
	struct tree{int ls,rs,sum;}t[N*40];
	int rt[N],tot;
	inline void insert(int o,int &p,int l,int r,int x){
		p=++tot;t[p]=t[o];t[p].sum++;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(x<=mid) insert(t[o].ls,t[p].ls,l,mid,x);
		else insert(t[o].rs,t[p].rs,mid+1,r,x);
	}
	inline int query(int p1,int p2,int l,int r,int ql,int qr){
		if(ql>qr||!(t[p2].sum-t[p1].sum)) return 0;
		if(l>=ql&&r<=qr) return t[p2].sum-t[p1].sum;
		int mid=(l+r)>>1,ret=0;
		if(ql<=mid) ret+=query(t[p1].ls,t[p2].ls,l,mid,ql,qr);
		if(qr>mid) ret+=query(t[p1].rs,t[p2].rs,mid+1,r,ql,qr);
		return ret;
	}
};
using namespace tr;

signed main(){
	n=read();Q=read();
	scanf("%s",ss+1);
	init();
	for(int i=1;i<=n;i++) insert(rt[i-1],rt[i],1,n,rk[i]);
	while(Q--){
		int a=read(),b=read();
		int c=read(),d=read();
		int L=0,R=min(b-a+1,d-c+1),ans=0;
		while(L<=R){
			int mid=(L+R)>>1;
			int pl=0,pr=0;
			int l=1,r=rk[c];
			while(l<=r){
				int md=(l+r)>>1;
				if(lcp(md+1,rk[c])>=mid) r=md-1,pl=md;
				else l=md+1;
			}
			l=rk[c];r=n;
			while(l<=r){
				int md=(l+r)>>1;
				if(lcp(rk[c]+1,md)>=mid) l=md+1,pr=md;
				else r=md-1;
			}
			if(query(rt[a-1],rt[(mid? b-mid+1:b)],1,n,pl,pr)) ans=mid,L=mid+1;
			else R=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2020-06-14 16:34  permzf  阅读(181)  评论(0编辑  收藏  举报