[HEOI2016]字符串

Posted by Dispwnl on December 30, 2018

题目

题目描述

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

输入输出格式

输入格式:

输入的第一行有两个正整数n,m,分别表示字符串的长度和询问的个数。接下来一行是一个长为n的字符串。接下来m行,每行有4个数a,b,c,d,表示询问s[a..b]的所有子串和s[c..d]的最长公共前缀的最大值。

输出格式:

对于每一次询问,输出答案。

输入输出样例

输入样例#1:

5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4

输出样例#1:

1
1
2
2
2

说明

对于10%的数据,1<=n,m<=3,00,

对于40%的数据,1<=n,m<=3,000,字符串中仅有a,b

对于100%的数据,1<=n,m<=100,000,字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n

题解

发现这样一个性质:

如果长度$x$是一个公共前缀,那么长度$x’$也是一个公共前缀

所以可以二分答案了,这样问题就转化成了求是否存在长度为$x$的公共前缀

$SA$做法:

因为$c$的位置已经给定了,所以可以求出来与后缀$[c,n]$的$lcp\ge x$的$rank$区间$[L,R]$,这个可以二分求定

现在就需要检验$[a,b-x+1]$中是否存在$rank_i$在$[L,R]$内,对字符串建立$rank$为值域的主席树,每次区间查询即可

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<algorithm>
# define tl s[k].l
# define tr s[k].r
# define mid (l+r>>1)
using namespace std;
const int MAX=1e5+5;
struct p{
	int x,l,r;
}s[MAX<<5];
int n,m,tot,M;
int tax[MAX],sa[MAX],rk[MAX],het[MAX],h[MAX],Log[MAX],rt[MAX];
int f[21][MAX];
char a[MAX];
bool cmp(int *a,int x,int y,int w) {return a[x]==a[y]&&a[x+w]==a[y+w];}
void rsort()
{
	for(int i=0;i<=m;++i)
	  tax[i]=0;
	for(int i=1;i<=n;++i)
	  ++tax[rk[h[i]]];
	for(int i=1;i<=m;++i)
	  tax[i]+=tax[i-1];
	for(int i=n;i>=1;--i)
	  sa[tax[rk[h[i]]]--]=h[i];
}
void Suffix()
{
	for(int i=1;i<=n;++i)
	  rk[i]=a[i],h[i]=i;
	m=127,rsort();
	for(int p=1,w=1,i;p<n;w+=w,m=p)
	  {
	  	for(p=0,i=n-w+1;i<=n;++i)
	  	  h[++p]=i;
	  	for(i=1;i<=n;++i)
	  	  if(sa[i]>w) h[++p]=sa[i]-w;
	  	rsort(),swap(rk,h),rk[sa[1]]=p=1;
	  	for(i=2;i<=n;++i)
	  	  rk[sa[i]]=cmp(h,sa[i],sa[i-1],w)?p:++p;
	  }
	int j,k=0;
	for(int i=1;i<=n;het[rk[i++]]=k)
	  for(k=k?k-1:k,j=sa[rk[i]-1];a[j+k]==a[i+k];++k);
}
int change(int pre,int l,int r,int x)
{
	int k=++tot;
	s[k]=s[pre],++s[k].x;
	if(l==r) return k;
	if(x<=mid) tl=change(s[pre].l,l,mid,x);
	else tr=change(s[pre].r,mid+1,r,x);
	return k;
}
int ask(int l,int r,int pre,int k,int L,int R)
{
	if(l>R||r<L) return 0;
	if(l>=L&&r<=R) return s[k].x-s[pre].x;
	return ask(l,mid,s[pre].l,tl,L,R)+ask(mid+1,r,s[pre].r,tr,L,R);
}
int read()
{
	int x(0);
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar());
	for(;isdigit(ch);x=x*10+ch-48,ch=getchar());
	return x;
}
void Init()
{
	Log[0]=-1;
	for(int i=1;i<=n;++i)
	  Log[i]=Log[i>>1]+1,f[0][i]=het[i],rt[i]=change(rt[i-1],1,n,rk[i]);
	for(int i=1;i<=20;++i)
	  for(int j=1;j<=n;++j)
	    f[i][j]=min(f[i-1][j],f[i-1][min(n,j+(1<<(i-1)))]);
}
int GET_LCP(int l,int r)
{
	if(l==r) return n-sa[l]+1;
	int t=Log[r-l];
	return min(f[t][l+1],f[t][r-(1<<t)+1]);
}
bool look(int L,int a,int b,int c)
{
	int l=c,r=n,ans=c,_L,_R;
	while(l<=r)
	{
		if(GET_LCP(c,mid)>=L) ans=mid,l=mid+1;
		else r=mid-1;
	}
	_R=ans,l=1,r=ans=c;
	while(l<=r)
	{
		if(GET_LCP(mid,c)>=L) ans=mid,r=mid-1;
		else l=mid+1;
	}
	_L=ans;
	return ask(1,n,rt[a-1],rt[b-L+1],_L,_R);
}
int main()
{
	n=read(),M=read(),scanf("%s",a+1);
	Suffix(),Init();
	for(int i=1,a,b,c,d,l,r,ans;i<=M;++i)
	  {
	  	a=read(),b=read(),c=read(),d=read(),l=1,ans=0,r=min(b-a+1,d-c+1);
	  	while(l<=r)
	  	{
	  		if(look(mid,a,b,rk[c])) ans=mid,l=mid+1;
	  		else r=mid-1;
		}
		printf("%d\n",ans);
	  }
	return 0;
}

$SAM​$做法:

同样是二分答案$mid$,令$pos=c+mid$,只需要找$right$集合中有$pos$的状态ta的$len$是否大于等于$mid$并且在区间$[a,b]​$内

记录每个位置在$SAM$上对应的节点,倍增查找第一个$len$大于等于$mid$的节点,线段树合并判断$right$集合内是否有位置在区间$[a,b]$里就行了

$luogu$开$O2$会TLE……不知道为什么QAQ

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<algorithm>
# define tl s[k].l
# define tr s[k].r
# define mid (l+r>>1)
using namespace std;
const int MAX=2e5+5;
struct p{
	int l,r;
}s[MAX<<5];
int n,m,tot;
int rt[MAX];
char a[MAX];
void change(int l,int r,int &k,int x)
{
	if(!k) k=++tot;
	if(l==r) return;
	if(x<=mid) change(l,mid,tl,x);
	else change(mid+1,r,tr,x);
}
int merge(int x,int y)
{
	if(!x||!y) return x+y;
	int k=++tot;
	return tl=merge(s[x].l,s[y].l),tr=merge(s[x].r,s[y].r),k;
}
bool ask(int l,int r,int k,int L,int R)
{
	if(R<l||r<L||!k) return 0;
	if(l>=L&&r<=R) return 1;
	if(ask(l,mid,tl,L,R)) return 1;
	return ask(mid+1,r,tr,L,R);
}
struct SAM{
	int l,r,L;
	int len[MAX],fa[MAX],tax[MAX],id[MAX],pos[MAX];
	int son[MAX][26],f[19][MAX];
	SAM() {l=r=1;}
	void ins(int x,int id)
	{
		int tt=r;
		len[r=++l]=++L,change(1,n,rt[r],id),pos[id]=r;
		for(;tt&&!son[tt][x];tt=fa[tt])
		  son[tt][x]=r;
		if(!tt) return void(fa[r]=1);
		int qwq=son[tt][x];
		if(len[qwq]==len[tt]+1) return void(fa[r]=qwq);
		len[++l]=len[tt]+1,fa[l]=fa[qwq],fa[r]=fa[qwq]=l;
		for(int i=0;i<26;++i)
		  son[l][i]=son[qwq][i];
		for(int i=tt;son[i][x]==qwq;i=fa[i])
		  son[i][x]=l;
	}
	void Init()
	{
		for(int i=1;i<=l;++i)
		  ++tax[len[i]],f[0][i]=fa[i];
		for(int i=1;i<=18;++i)
		  for(int j=1;j<=l;++j)
		    f[i][j]=f[i-1][f[i-1][j]];
		for(int i=1;i<=n;++i)
		  tax[i]+=tax[i-1];
		for(int i=l;i>=1;--i)
		  id[tax[len[i]]--]=i;
		for(int i=l;i>=1;--i)
		  if(fa[id[i]]) rt[fa[id[i]]]=merge(rt[fa[id[i]]],rt[id[i]]);
	}
	bool look(int A,int B,int c,int __L)
	{
		int d=c+__L-1,x=pos[d];
		if(len[x]<__L) return 0;
		for(int i=18;i>=0;--i)
		  if(len[f[i][x]]>=__L) x=f[i][x];
		return ask(1,n,rt[x],A+__L-1,B);
	}
}_S;
int read()
{
	int x(0);
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar());
	for(;isdigit(ch);x=x*10+ch-48,ch=getchar());
	return x;
}
int main()
{
	n=read(),m=read(),scanf("%s",a+1);
	for(int i=1;i<=n;++i)
	  _S.ins(a[i]-'a',i);
	_S.Init();
	for(int i=1,a,b,c,d,l,r,ans;i<=m;++i)
	  {
	  	a=read(),b=read(),c=read(),d=read(),l=1,r=d-c+1,ans=0;
	  	while(l<=r)
	  	{
	  		if(_S.look(a,b,c,mid)) ans=mid,l=mid+1;
	  		else r=mid-1;
		}
		printf("%d\n",ans);
	  }
	return 0;
}