[bzoj4556][Tjoi2016&Heoi2016]字符串——后缀数组+主席数+二分答案

题目大意:

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CEO,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a…b]的所有子串和s[c…d]的最长公共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?

思路:

考虑用后缀数组解决问题,后缀数组的求lcp即进行区间最小值,这里用[a,b]的每一个后缀去和c求lcp。
考虑如何优化上述过程,因为后缀的lcp具有可二分性,同又由于它是区间最小值,于是我们二分一个答案后将问题转化为判定性问题。
二分一个答案ans,同事我们可以将c的sa中的位置向左向右拓展,得到的区间即另外一个后缀所在的位置,我们只需要判断这个区间里面有没有我们需要的后缀即可,由于每次询问的区间都是连续的,直接用主席树维护即可。

/*=======================================
 * Author : ylsoi
 * Time : 2019.2.9
 * Problem : bzoj4556
 * E-mail : ylsoi@foxmail.com
 * ====================================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj4556.in","r",stdin);
	freopen("bzoj4556.out","w",stdout);
}

template<typename T>void read(T &_){
	_=0; T f=1; char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
	_*=f;
}

const int maxn=1e5+10;
int n,m;
char s[maxn];
int height[maxn<<1],sa[maxn<<1],rk[maxn<<1];

namespace Suffix_Array{
	int sz,tax[maxn<<1],tp[maxn<<1];
	void radix_sort(){
		REP(i,1,sz)tax[i]=0;
		REP(i,1,n)++tax[rk[i]];
		REP(i,1,sz)tax[i]+=tax[i-1];
		DREP(i,n,1)sa[tax[rk[tp[i]]]--]=tp[i];
	}
	void suffix_sort(){
		sz=26;
		REP(i,1,n)rk[i]=s[i]-'a'+1,tp[i]=i;
		radix_sort();
		for(int w=1,p=0;w<n;w<<=1){
			p=0;
			REP(i,1,w)tp[++p]=n-w+i;
			REP(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
			radix_sort();
			swap(rk,tp);
			rk[sa[1]]=p=1;
			REP(i,2,n)
				if(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+w]==tp[sa[i]+w])rk[sa[i]]=p;
				else rk[sa[i]]=++p;
			sz=p;
			if(sz==n)break;
		}
		int p=0;
		REP(i,1,n){
			if(p)--p;
			int j=sa[rk[i]-1];
			while(s[i+p]==s[j+p])++p;
			height[rk[i]]=p;
		}
	}
}

struct Chairman_Tree{
#define mid ((l+r)>>1)
	struct node{
		int lc,rc,sum;
	}t[maxn<<5];
	int root[maxn],cnt;
	void insert(int &o,int l,int r,int x){
		int now=++cnt;
		t[now]=t[o],o=now,++t[o].sum;
		if(l==r)return;
		if(x<=mid)insert(t[o].lc,l,mid,x);
		else insert(t[o].rc,mid+1,r,x);
	}
	int query(int o1,int o2,int l,int r,int L,int R){
		if(L<=l && r<=R)return t[o2].sum-t[o1].sum;
		else{
			int ret=0;
			if(L<=mid)ret+=query(t[o1].lc,t[o2].lc,l,mid,L,R);
			if(R>=mid+1)ret+=query(t[o1].rc,t[o2].rc,mid+1,r,L,R);
			return ret;
		}
	}
#undef mid
}T;

int st[maxn][17],Log[maxn];

void init_rmq(){
	REP(i,2,n)Log[i]=Log[i>>1]+1;
	REP(i,1,n)st[i][0]=height[i];
	REP(j,1,Log[n])REP(i,1,n-(1<<j)+1)
		st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}

int solve(int a,int b,int c,int d){
	int l=0,r=min(b-a+1,d-c+1),p=rk[c];
	if(a<=c && b>=c)l=min(b-c+1,d-c+1);
	while(l<r){
		int mid=(l+r+1)>>1;
		if(height[p]>=mid){
			int L=p;
			DREP(i,Log[p],0)if(L-(1<<i)>=2 && st[L-(1<<i)][i]>=mid)L-=(1<<i);
			if(T.query(T.root[L-2],T.root[p-1],1,n,a,b-mid+1)){
				l=mid;
				continue;
			}
		}
		if(height[p+1]>=mid){
			int R=p+1;
			DREP(i,Log[n-p],0)if(R+(1<<i)<=n && st[R+1][i]>=mid)R+=(1<<i);
			if(T.query(T.root[p],T.root[R],1,n,a,b-mid+1)){
				l=mid;
				continue;
			}
		}
		r=mid-1;
	}
	return l;
}

int main(){
	File();
	read(n),read(m);
	scanf("%s",s+1);
	Suffix_Array::suffix_sort();
	REP(i,1,n){
		T.root[i]=T.root[i-1];
		T.insert(T.root[i],1,n,sa[i]);
	}
	init_rmq();
	int a,b,c,d;
	REP(i,1,m){
		read(a),read(b),read(c),read(d);
		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、付费专栏及课程。

余额充值