BZOJ 4556: [Tjoi2016&Heoi2016]字符串 后缀数组 主席树

4556: [Tjoi2016&Heoi2016]字符串

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 1066  Solved: 429
[Submit][Status][Discuss]

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

1
1
2
2
2

这个题的思路真是很精巧啊

感觉对后缀数组的认识又全面了些??唉 其实本质还是思想江化啊


说题解

借用一下neither_nor的图。


然后自然而然就会想到用SA来解决问题(蒟蒻还不会SAM)

暴力扫a->b肯定是不行的

那么可以怎么搞呢。。

BJ最开始YY了一种树套树解法:

。。。写了半天最后删掉了,发现好像需要可持久化一些东西,然后比较懵逼。。。

放弃梦想,去查题解


考虑可以二分这个长度len(BJ根本没想到 /捂脸熊)

然后怎么判呢~

考虑从c处向两侧的后缀扩展会得到所有满足条件的后缀

之后只需要判有没有a->b-len+1的后缀在这个区间就行了

这个怎么搞呢

考虑主席树有区间分割的功效

BJ是建了一个suffix为下标rank为权值的主席树

然后每次询问在固定权值范围内是否有返回值就行了


之后又去看了一下commoc的代码

他是以rank为下标SA为权值,询问每次是否有SA在a->b内

都可以


这个题调了整整三天,最后发现RMQ里面用的是log,然后愤怒改成log2。。

还有就是,预处理log2要比调用函数快。。。

#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=100100;

char s[N];
int  n,ht[N],sa1[N],sa2[N],rk1[N],rk2[N],buc[N];
int *sa=sa1,*tp=sa2,*rk=rk1,*tmp=rk2;

void getsa()
{
	register int i,j,k;
	for(i=1;i<=n;++i)buc[s[i]]++;
	for(i=1;i<=300;++i)buc[i]+=buc[i-1];
	for(i=n;i;i--)sa[buc[s[i]]--]=i;
	for(i=1;i<=n;++i)rk[sa[i]]=rk[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
	
	for(k=1;k<=n;k<<=1)
	{
		for(i=1;i<=n;++i)buc[rk[sa[i]]]=i;
		for(i=n;i;i--)if(sa[i]>k)tp[buc[rk[sa[i]-k]]--]=sa[i]-k;
		for(i=n-k+1;i<=n;++i)tp[buc[rk[i]]--]=i;
		bool flag=0;
		for(i=1;i<=n;++i)
		{
			tmp[tp[i]]=tmp[tp[i-1]]+(rk[tp[i]]!=rk[tp[i-1]]||rk[tp[i]+k]!=rk[tp[i-1]+k]);
			if(tmp[tp[i]]==n)flag=1;
		}
		swap(tmp,rk);swap(tp,sa);
		if(flag)break;
	}
	
	k=0;
	for(i=1;i<=n;++i)
	{
		j=rk[i]-1;
		while(s[sa[rk[i]]+k]==s[sa[j]+k])k++;
		ht[rk[i]]=k;if(k)k--;
	}
}

int root[N];

struct president_tree{int w,ls,rs;}tr[N<<5];
int sz;

void insert(int &k,int x,int l,int r,int val)
{
	k=++sz;
	tr[k].w=tr[x].w+1;if(l==r)return ;
	tr[k].ls=tr[x].ls;tr[k].rs=tr[x].rs;
	int mid=(l+r)>>1;
	val<=mid?insert(tr[k].ls,tr[x].ls,l,mid,val):insert(tr[k].rs,tr[x].rs,mid+1,r,val);
}

int mn[N<<1][20],bas[N];

void initial()
{
	register int i,j;
	for(i=1;i<=n;++i)insert(root[i],root[i-1],1,n,rk[i]);
	for(i=1;i<=n;++i)mn[i][0]=ht[i];
	for(j=1;(1<<j)<=n;++j)for(i=1;i+(1<<(j-1))<=n;++i)
	mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
	for(i=1;i<=n;++i)bas[i]=(1<<(bas[i-1]+1))==i?bas[i-1]+1:bas[i-1];
}

inline int query_mn(int l,int r)
{int k=bas[r-l+1];return min(mn[l][k],mn[r-(1<<k)+1][k]);}

int query(int a,int b,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)return tr[b].w-tr[a].w;
	int mid=(l+r)>>1;
	if(x>mid)return query(tr[a].rs,tr[b].rs,mid+1,r,x,y);
	if(y<=mid)return query(tr[a].ls,tr[b].ls,l,mid,x,y);
	return query(tr[a].ls,tr[b].ls,l,mid,x,y)+query(tr[a].rs,tr[b].rs,mid+1,r,x,y);
}

bool check(int aim,int pos,int x,int y)
{
	register int l,r,mid,L=rk[pos]+1,R=rk[pos];
	if(ht[rk[pos]]>=aim)
	{
		l=2,r=rk[pos];
		while(l<=r)
		{
			mid=(l+r)>>1;
			query_mn(mid,rk[pos])<aim?l=mid+1:(r=mid-1,L=mid);
		}
	}
	if(ht[rk[pos]+1]>=aim)
	{
		l=rk[pos]+1;r=n;
		while(l<=r)
		{
			mid=(l+r)>>1;
			query_mn(rk[pos]+1,mid)<aim?r=mid-1:(l=mid+1,R=mid);
		}
	}
	return query(root[x],root[y],1,n,L-1,R);
}

void solve()
{
	register int a,b,c,d,l,r,mid,res=0;
	a=read();b=read();c=read();d=read();
	if(a==c){print(min(b,d)-a+1);puts("");return ;}
	l=1;r=b-a+1;
	while(l<=r)
	{
		mid=(l+r)>>1;
		check(mid,c,a-1,b-mid+1)?(l=mid+1,res=mid):r=mid-1;
	}
	res=min(res,d-c+1);
	print(res);puts("");
}

int main()
{
	n=read();register int Q=read();
	scanf("%s",s+1);
	getsa();
	initial();
	while(Q--){solve();}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值