题解 [TJOI / HEOI2016] 字符串(LOJ #2059 / 洛谷 P4094)【二分答案 主席树 ST表】

题目链接:洛谷 P4094 / LOJ #2059

题意

给定长为 n n n 的字符串, m m m 次询问子串 [ a … b ] [a\dots b] [ab] 的子串 [ c … d ] [c\dots d] [cd] 这个子串的最长公共前缀的长度的最大值。 n , m ≤ 1 0 5 n,m\leq 10^5 n,m105。时限 2s。

题解

显然,假如能找到长为 x x x 的公共前缀,那么一定能找到比 x x x 短的公共前缀。所以首先二分 a n s ans ans

一番特判之后可以不管子串的右端点,直接把它当作后缀来处理,把一个后缀 s u f ( i ) suf(i) suf(i) 记录为点 ( s a [ i ] , i ) (sa[i],i) (sa[i],i)

我们知道 lcp ⁡ ( a , b ) = min ⁡ i = a + 1 b h [ i ] \operatorname{lcp}(a,b)=\min\limits_{i=a+1}^{b}h[i] lcp(a,b)=i=a+1minbh[i],所以满足 lcp ⁡ ( c , s a [ x ] ) ≥ a n s \operatorname{lcp}(c,sa[x])\geq ans lcp(c,sa[x])ans x x x 肯定是连续一段。二分它在 r k [ c ] rk[c] rk[c] 左边和右边分别能延伸多远(要求达到 O ( 1 ) O(1) O(1) 查询 lcp ⁡ \operatorname{lcp} lcp,需要用 ST 表),可以得到 i i i 的范围。

又知道 a ≤ i ≤ b a\leq i\leq b aib,所以每次 check 就相当于数一个矩形范围内的点数,有点则 a n s ans ans 可行。统计这个可以用主席树。

在这里插入图片描述
复杂度: O ( n log ⁡ n + m log ⁡ 2 n ) O(n\log n+m\log^2n) O(nlogn+mlog2n)(预处理+二分套(二分套ST+主席树))(自带超大常数)

代码:

/**********
Author: WLBKR5
Problem: loj 2059, luogu P4094
Name: 字符串
Source: HEOI2016, TJOI2016
Algorithm: 后缀数组, 主席树, St 表 
Date: 2020/05/28
Statue: accepted
Submission: loj.ac/submission/818921, www.luogu.com.cn/record/33956491
**********/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll getint(){
	ll ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
const int N=1e5+10,L=17;
int n;
//------SA------
char s[N];
int sa[N],rk[N],tp[N],h[N];
int c[N];
bool cmp(int x,int y,int w){
	return tp[x]==tp[y]&&tp[x+w]==tp[y+w];
}
void rsort(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[tp[i]]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;--i)sa[c[rk[tp[i]]]--]=tp[i];
}
void get_sa(){
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	int m=127;
	rsort(m);
	for(int k=1,p=0;p<n;m=p,k<<=1){
		p=0;
		for(int i=n-k+1;i<=n;i++)tp[++p]=i;
		for(int i=1;i<=n;i++)
			if(sa[i]>k) tp[++p]=sa[i]-k;
		rsort(m);
		memcpy(tp,rk,sizeof(tp));
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++){
			rk[sa[i]]=cmp(sa[i],sa[i-1],k)?p:++p;
		}
	}
} 
void get_height(){
	int t=0;
	for(int i=1;i<=n;i++){
		if(t)--t;
		while(s[sa[rk[i]-1]+t]==s[i+t])++t;
		h[rk[i]]=t;
	}
}
//------ST------
int st[L][N];//min [i,i+2^j)
int l2[N];
void init_st(){
	for(int i=1;i<=n;i++){
		st[0][i]=h[i+1];
	}
	for(int i=1;i<L;i++){
		for(int j=1;j<=n-(1<<(i-1));j++){
			st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
		}
	}
}
int get_min(int l,int r){ //(l,r]
	if(l==r)return n-sa[l]+1;
	return min(st[l2[r-l]][l],st[l2[r-l]][r-(1<<l2[r-l])]);
}
//------chairman tree------
int ch[N*(L+1)][2],sz[N*(L+1)],rt[N],cnt=0;//(sa[i],i)
void add(int x,int val){
	rt[x]=++cnt;
	int u=rt[x],v=rt[x-1];
	for(int i=L-1;i>=0;i--){
		int t=(val>>i)&1;
		sz[u]=sz[v]+1;
		ch[u][t]=++cnt;
		ch[u][t^1]=ch[v][t^1];
		u=ch[u][t];v=ch[v][t];
	}
	sz[u]=sz[v]+1;
}
int query(int w,int x,int y){
	w=rt[w];x=rt[x];
	int ans=0;
	for(int i=L-1;i>=0;i--){
		int t=(y>>i)&1;
		if(t)ans+=sz[ch[x][0]]-sz[ch[w][0]];
		w=ch[w][t];x=ch[x][t];
	}
	ans+=sz[x]-sz[w];
	return ans;
}
int query(int a,int b,int x,int y){
	if(b<a||y<x)return 0;
	return query(a-1,b,y)-query(a-1,b,x-1)>0;
}

void gett(int c,int len,int &x,int &y){
	c=rk[c];
	int l=1,r=c,mid=0;
	while(l<=r){
		mid=l+r>>1;
		//cerr<<"gett1 "<<"("<<rk[c]<<") "<<len<<" "<<n<<" | "<<l<<" "<<r<<" "<<mid<<endl;
		//cerr<<"    "<<get_min(mid,rk[c])<<endl;
		if(get_min(mid,c)>=len)x=mid,r=mid-1;
		else l=mid+1;
	}
	l=c,r=n;
	while(l<=r){
		mid=l+r>>1;
		//cerr<<"gett2 "<<c<<" "<<len<<" "<<n<<" "<<mid<<endl;
		if(get_min(c,mid)>=len)y=mid,l=mid+1;
		else r=mid-1;
	}
}
bool check(int a,int b,int c,int d,int mid){
	b=b-mid+1;
	if(b<a)return 0;
	//cerr<<"check "<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<mid<<" "<<n<<endl;
	int x=0,y=0;
	gett(c,mid,x,y);
	//cerr<<"    check - p: "<<p.first<<" "<<p.second<<endl;
	return query(x,y,a,b);
	//cerr<<"    check - q: "<<q<<endl;
}
int solve(int a,int b,int c,int d){
	int l=1,r=min(b-a+1,d-c+1),mid=0,ans=0;
	while(l<=r){
		mid=l+r>>1;
		if(check(a,b,c,d,mid))ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}

void init(){
	l2[0]=-1;
	for(int i=1;i<=n;i++)l2[i]=l2[i>>1]+1;
	get_sa();
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	//for(int i=1;i<=n;i++)cerr<<rk[i]<<" ";cerr<<endl;
	get_height();
	for(int i=1;i<=n;i++)cerr<<h[i]<<" ";cerr<<endl;
	init_st();
	for(int i=1;i<=n;i++)add(i,sa[i]);
}
int main(){
	n=getint();
	int m=getint();
	scanf("%s",s+1);
	init();
	while(m--){
		int a=getint(),b=getint(),c=getint(),d=getint();
		printf("%d\n",solve(a,b,c,d));
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值