• [HEOI2016/TJOI2016]字符串


    XVIII.[HEOI2016/TJOI2016]字符串

    作为一个理智正常的OIer,二维数点的题说什么都应该离线线段树通过而不是大力搞主席树呀(((

    我们发现这题询问中\(s[c,\dots,d]\)中这个“\(d\)”是不重要的,只需要把最终结果同\((d-c+1)\)\(\min\)即可,因此忽略不管。

    然后这个问题就被转换成了:对于区间\([a,b]\)内所有后缀\(suf[i]\),求\(\min\Big(\operatorname{LCP}(suf[i],suf[c]),b-i+1\Big)\)的最大值。

    我们考虑二分这个最大值,设为\(mid\)。现在要来判断\(mid\)这个值是否合法。则只有区间\([a,a+mid-1]\)中的\(\operatorname{LCP}\)才可能达到这么长,故只要找到其中的\(\max\text{LCP}\),如果其长度大于等于\(mid\),则\(mid\)合法。

    到现在我们已经可以构思出一个\(O(n\log^2n)\)的二分套线段树的做法了。具体思路是,因为\(\max\text{LCP}\)一定在两个后缀的\(rk\)最接近时取到,所以我们将它拆成两半,一半是\(rk_i\leq rk_c\)的,一半是\(rk_i\geq rk_c\)的。

    则我们需要先将询问按照\(rk_c\)排序,按顺序将位置插入线段树,在前一半中查询区间中\(rk_i\)的最大值,后一半中查询\(rk_i\)的最小值(这里的线段树是以原串位置为下标的)。在查询到这个值最大值/最小值后,就可以通过ST表求出\(\text{LCP}\)了。当然,这一切都是建立在二分的基础上,即,我们每次询问的区间都是二分出来的区间\([a,a+mid-1]\)

    但是这样子得写两棵线段树,再加上ST表,太难受了。我们不如这样,直接在线段树上维护\(\text{LCP}\)长度。办法很简单,当新加入一个位置后,直接将线段树中所有\(\text{LCP}\)长度与它取\(\min\)即可。

    我们可以写出这样的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])即可。

    在取\(\min\)后,把对应位置的\(\text{LCP}\)长度改成\(n-i\),因为自己跟自己的\(\text{LCP}\)长度就是串长。

    这里是将一个位置插入线段树的代码:

    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函数来回答询问。我们考虑当前走到节点\(x\),它代表的区间是\([l,r]\),询问区间是\([L,R]\)

    1. 假如\([l,r]\cup[L,R]=\emptyset\),直接return -1即可。

    2. 假如\([l,r]\subseteq[L,R]\)

    2.1. 如果\(lcp_x\geq R-r+1\),则显然全局最优答案肯定处于区间\([l,r]\)之中,我们单独开一个getans函数来处理这种情况,马上讲。我们直接返回getans的结果,并标记找到了答案。

    2.2 否则,\(lcp_x\)就是区间\([l,r]\)的最优答案(因为没有越界),直接return lcp[x]即可。

    1. 否则,先递归左儿子,如果左儿子找到答案,直接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));
    }
    

    正着来一遍,反着再来一遍,两边答案取\(\max\)即可。

    总复杂度\(O(n\log n)\)

    代码:

    #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;
    }
    

  • 相关阅读:
    K8S calico
    K9S之glusterfs
    kubeadm安装报错
    创建crd和cr
    分布式学习汇总
    容器常见问题分析
    项目迁移到k8s平台流程
    K8S storageclass
    awk命令
    K8S headless service服务详解
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605279.html
Copyright © 2020-2023  润新知