[HEOI2016/TJOI2016]字符串
XVIII.[HEOI2016/TJOI2016]字符串
作为一个理智正常的OIer,二维数点的题说什么都应该离线线段树通过而不是大力搞主席树呀(((
我们发现这题询问中中这个“”是不重要的,只需要把最终结果同取即可,因此忽略不管。
然后这个问题就被转换成了:对于区间内所有后缀,求的最大值。
我们考虑二分这个最大值,设为。现在要来判断这个值是否合法。则只有区间中的才可能达到这么长,故只要找到其中的,如果其长度大于等于,则合法。
到现在我们已经可以构思出一个的二分套线段树的做法了。具体思路是,因为一定在两个后缀的最接近时取到,所以我们将它拆成两半,一半是的,一半是的。
则我们需要先将询问按照排序,按顺序将位置插入线段树,在前一半中查询区间中的最大值,后一半中查询的最小值(这里的线段树是以原串位置为下标的)。在查询到这个值最大值/最小值后,就可以通过ST表求出了。当然,这一切都是建立在二分的基础上,即,我们每次询问的区间都是二分出来的区间。
但是这样子得写两棵线段树,再加上ST表,太难受了。我们不如这样,直接在线段树上维护长度。办法很简单,当新加入一个位置后,直接将线段树中所有长度与它取即可。
我们可以写出这样的pushdown
函数:
#define change(x,y) seg[x].lcp=min(seg[x].lcp,y),seg[x].tag=min(seg[x].tag,y)
void pushdown(int x){
change(lson,seg[x].tag),change(rson,seg[x].tag),seg[x].tag=0x3f3f3f3f;
}
然后在主函数只需要这样来一句change(1,ht[j])
即可。
在取后,把对应位置的长度改成,因为自己跟自己的长度就是串长。
这里是将一个位置插入线段树的代码:
void turnon(int x,int l,int r,int P){
if(l>P||r<P)return;
seg[x].lcp=max(seg[x].lcp,n-P);
if(l!=r)pushdown(x),turnon(lson,l,mid,P),turnon(rson,mid+1,r,P);
}
到这里,我们已经省掉了ST表,并且也只用写一颗线段树了。我们要不再努力努力,干脆连二分也给省了,直接在线段树上二分?
我们设一个query
函数来回答询问。我们考虑当前走到节点,它代表的区间是,询问区间是。
-
假如,直接
return -1
即可。 -
假如:
2.1. 如果,则显然全局最优答案肯定处于区间之中,我们单独开一个getans
函数来处理这种情况,马上讲。我们直接返回getans
的结果,并标记找到了答案。
2.2 否则,就是区间中的最优答案(因为没有越界),直接return lcp[x]
即可。
- 否则,先递归左儿子,如果左儿子找到答案,直接
return 左儿子的答案
即可;否则,return max(左儿子的答案,右儿子的答案)
至于getans
函数,就是一个常规的线段树上二分,找到最小的那个满足lcp[x]>=R-r+1
的位置并返回即可。
这部分代码:
int getans(int x,int l,int r,int L,int R){//this function is to find the real answer in a section
if(l==r)return min(seg[x].lcp,R-r+1);//we've reached a leaf, go back immediately.
pushdown(x);
if(seg[lson].lcp>=R-mid+1)return getans(lson,l,mid,L,R);//the answer in left son is surely the best
else return max(seg[lson].lcp,getans(rson,mid+1,r,L,R));//the left answer hasn't gone over the border, so it's the real answer
}
int query(int x,int l,int r,int L,int R,bool &findans){//this function is to find the answer to the queries
if(l>R||r<L)return -1;
if(L<=l&&r<=R){
if(seg[x].lcp>=R-r+1){findans=true;return getans(x,l,r,L,R);}//the answer here is the best answer
return seg[x].lcp;//the answer has't gone over the border, so it's the real answer
}
pushdown(x);
int tmp=query(lson,l,mid,L,R,findans);
if(findans)return tmp;
return max(tmp,query(rson,mid+1,r,L,R,findans));
}
正着来一遍,反着再来一遍,两边答案取即可。
总复杂度。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100100;
int n,m,q,res[N],len[N];
namespace Suffix_Array{
int x[N],y[N],sa[N],ht[N],rk[N],buc[N];
char s[N];
bool mat(int a,int b,int k){
if(y[a]!=y[b])return false;
if((a+k<n)^(b+k<n))return false;
if((a+k<n)&&(b+k<n))return y[a+k]==y[b+k];
return true;
}
void SA(){
for(int i=0;i<n;i++)buc[x[i]=s[i]]++;
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n-1;i>=0;i--)sa[--buc[x[i]]]=i;
for(int k=1;k<n;k<<=1){
int num=0;
for(int i=n-k;i<n;i++)y[num++]=i;
for(int i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
for(int i=0;i<=m;i++)buc[i]=0;
for(int i=0;i<n;i++)buc[x[y[i]]]++;
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n-1;i>=0;i--)sa[--buc[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=num=0;
for(int i=1;i<n;i++)x[sa[i]]=mat(sa[i],sa[i-1],k)?num:++num;
m=num;
}
for(int i=0;i<n;i++)rk[sa[i]]=i;
for(int i=0,k=0;i<n;i++){
if(!rk[i])continue;
if(k)k--;
int j=sa[rk[i]-1];
while(i+k<n&&j+k<n&&s[i+k]==s[j+k])k++;
ht[rk[i]]=k;
}
}
}
using namespace Suffix_Array;
struct Query{
int L,R,pos,id;
Query(int a=0,int b=0,int c=0,int d=0){L=a,R=b,pos=c,id=d;}
friend bool operator <(const Query&x,const Query&y){
return x.pos<y.pos;
}
}p[N];
namespace SegMentTree{
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct SegTree{//lcp stands for the maximum lcp in the section
int lcp,tag;
}seg[N<<2];
#define change(x,y) seg[x].lcp=min(seg[x].lcp,y),seg[x].tag=min(seg[x].tag,y)
void pushdown(int x){
change(lson,seg[x].tag),change(rson,seg[x].tag),seg[x].tag=0x3f3f3f3f;
}
void build(int x,int l,int r){
seg[x].tag=0x3f3f3f3f,seg[x].lcp=-1;
if(l!=r)build(lson,l,mid),build(rson,mid+1,r);
}
void turnon(int x,int l,int r,int P){
if(l>P||r<P)return;
seg[x].lcp=max(seg[x].lcp,n-P);
if(l!=r)pushdown(x),turnon(lson,l,mid,P),turnon(rson,mid+1,r,P);
}
int getans(int x,int l,int r,int L,int R){//this function is to find the real answer in a section
if(l==r)return min(seg[x].lcp,R-r+1);//we've reached a leaf, go back immediately.
pushdown(x);
if(seg[lson].lcp>=R-mid+1)return getans(lson,l,mid,L,R);//the answer in left son is surely the best
else return max(seg[lson].lcp,getans(rson,mid+1,r,L,R));//the left answer hasn't gone over the border, so it's the real answer
}
int query(int x,int l,int r,int L,int R,bool &findans){//this function is to find the answer to the queries
if(l>R||r<L)return -1;
if(L<=l&&r<=R){
if(seg[x].lcp>=R-r+1){findans=true;return getans(x,l,r,L,R);}//the answer here is the best answer
return seg[x].lcp;//the answer has't gone over the border, so it's the real answer
}
pushdown(x);
int tmp=query(lson,l,mid,L,R,findans);
if(findans)return tmp;
return max(tmp,query(rson,mid+1,r,L,R,findans));
}
#undef lson
#undef rson
#undef mid
}
using namespace SegMentTree;
int main(){
scanf("%d%d%s",&n,&q,s),m='z',SA();
for(int i=1,a,b,c,d;i<=q;i++)scanf("%d%d%d%d",&a,&b,&c,&d),p[i]=Query(a-1,b-1,rk[c-1],i),len[i]=d-c+1;
sort(p+1,p+q+1);
build(1,0,n-1);
for(int i=1,j=0;i<=q;i++){
for(;j<=p[i].pos;j++)change(1,ht[j]),turnon(1,0,n-1,sa[j]);
bool tmp=false;
res[p[i].id]=max(res[p[i].id],query(1,0,n-1,p[i].L,p[i].R,tmp));
}
build(1,0,n-1);
for(int i=q,j=n-1;i;i--){
for(;j>=p[i].pos;j--)change(1,ht[j+1]),turnon(1,0,n-1,sa[j]);
bool tmp=false;
res[p[i].id]=max(res[p[i].id],query(1,0,n-1,p[i].L,p[i].R,tmp));
}
for(int i=1;i<=q;i++)printf("%d\n",min(res[i],len[i]));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?