【洛谷P4482】[BJWC2018]Border 的四种求法(SAM)(线段树)(链分治)

传送门


题解:

思博题一写就是一下午。。。

首先区间border这个玩意是没有办法直接做的。

我还没有听说过有什么字符串理论是针对区间border的,我目前知道所有字符串border理论全部是针对全串的,如果您知道区间border的相关理论,欢迎打脸告知博主。

那么实际上转化成字符串术语,我们询问的是满足 i − l + 1 ≤ l c s ( i , r ) , l ≤ i < r i-l+1\leq lcs(i,r),l\leq i<r il+1lcs(i,r),li<r的最大的 i i i,其中 l c s lcs lcs表示的是两个前缀的最长公共后缀。

那么现在我们已经有一个想法了,暴跳 f a i l fail fail,线段树合并维护 r i g h t right right集合,由于 f a i l fail fail树上 L C A LCA LCA的长度就是 l c s lcs lcs的长度,所以现在相当于要询问 r i g h t right right集合中满足 l ≤ i ≤ min ⁡ ( r − 1 , l e n [ l c a ] + l − 1 ) l\leq i \leq \min(r-1,len[lca]+l-1) limin(r1,len[lca]+l1) i i i的最大值。

但是问题在于我们并不能暴跳 f a i l fail fail树,复杂度不对。

还是假设我们定了一个LCA,那么限制可以描述为 l ≤ i < r , i − l e n [ l c a ] + 1 ≤ l l\leq i<r,i-len[lca]+1\leq l li<r,ilen[lca]+1l

就是在 [ l , r ) [l,r) [l,r)中找到最靠右的,值比 l l l小的点。

我们发现好像可以对一条链动态维护这个东西,于是想到了链分治。

考虑我们现在在处理一条链,显然链上的点到链外的点的LCA可以直接考虑从链上每个点向链外每个子树dfs。

于是思路就很显然了,为了降低复杂度这部分需要轻重链分治dsu on tree,每次处理一条重链的询问,那么每个询问需要跳重链,挂到跳到的点上面。

但是这样子漏掉了一种情况。我们每次只处理的这个重链外的子树,但是这条重链下的子树没有考虑,所有还要来一次线段树合并,用重链下面的子树来更新答案。

其实本质就是先把暴跳fail树拆成跳重链。那么跳到重链的某个点上之后根据LCA的情况的不同考虑重链上方和重链下方对答案的贡献。这么说感觉挺简单的


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=4e5+7;

char s[N>>1];int n;

namespace SAM{
	cs int N=4e5+7;
	int fa[N],son[N][26],len[N],now;
	inline void init(){for(int re i=1;i<=n;++i)len[i]=i;now=n+1;}
	inline void push_back(int i,int c){
		int cur=i,p=(i-1)?(i-1):(n+1);
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p)fa[cur]=n+1;
		else if(len[son[p][c]]==len[p]+1)fa[cur]=son[p][c];
		else {
			int nq=++now,q=son[p][c];
			memcpy(son[nq],son[q],sizeof son[q]);
			len[nq]=len[p]+1,fa[nq]=fa[q];
			fa[q]=fa[cur]=nq;
			for(;p&&son[p][c]==q;p=fa[p])son[p][c]=nq;
		}
	}
	int bin[N],nd[N];
	inline void radix_sort(){
		for(int re i=1;i<=now;++i)++bin[len[i]];
		for(int re i=1;i<=now;++i)bin[i]+=bin[i-1];
		for(int re i=now;i;--i)nd[bin[len[i]]--]=i;
	}
}
using SAM::now;
using SAM::len;
using SAM::fa;
using SAM::nd;

std::vector<int> G[N];
int siz[N],son[N],top[N];
inline void tree_dissection(){
	for(int re i=now;i;--i){
		int u=nd[i];siz[fa[u]]+=++siz[u];
		if(siz[u]>siz[son[fa[u]]])son[fa[u]]=u;
		G[fa[u]].push_back(u);
	}
	for(int re i=1;i<=now;++i){
		int u=nd[i];if(!top[u])top[u]=u;
		if(son[u])top[son[u]]=top[u];
	}
}

std::vector<int> q[N];
int Q,Ql[N>>1],Qr[N>>1],ans[N>>1];

inline void addq(int id,int u){
	if(Ql[id]==Qr[id])return ;
	while(u)q[u].push_back(id),u=fa[top[u]];
}

namespace SGT1{
	cs int M=N*30;
	int rt[N],lc[M],rc[M],mx[M],tot;
	inline void ins(int &u,int l,int r,int p){
		u=++tot;if(l==r){mx[u]=p;return ;}int mid=l+r>>1;
		(p<=mid)?ins(lc[u],l,mid,p):ins(rc[u],mid+1,r,p);
		mx[u]=std::max(mx[lc[u]],mx[rc[u]]);
	}
	inline void merge(int &u,int v){
		if(!u||!v){u|=v;return ;}
		merge(lc[u],lc[v]);
		merge(rc[u],rc[v]);
		mx[u]=std::max(mx[lc[u]],mx[rc[u]]);
	}
	inline int query(int u,int l,int r,int ql,int qr){
		if((ql<=l&&r<=qr)||!u)return mx[u];int mid=l+r>>1;
		if(qr<=mid)return query(lc[u],l,mid,ql,qr);
		if(mid<ql)return query(rc[u],mid+1,r,ql,qr);
		return std::max(query(lc[u],l,mid,ql,qr),query(rc[u],mid+1,r,ql,qr));
	}
	inline void work(){
		for(int re i=1;i<=n;++i)ins(rt[i],1,n,i);
		for(int re i=now;i;--i){
			int u=nd[i];for(int re id:q[u])
			ans[id]=std::max(ans[id],
				query(rt[u],1,n,Ql[id],std::min(Qr[id]-1,len[u]+Ql[id]-1))-Ql[id]+1);
			merge(rt[fa[u]],rt[u]);
		}
	}
}

namespace SGT2{
	cs int M=N<<1|1;
	int lc[M],rc[M],mn[M],rt,tot;
	inline void ins(int &u,int l,int r,int p,int x){
		if(!u){u=++tot,lc[u]=rc[u]=0,mn[u]=1e9;}
		mn[u]=std::min(mn[u],x);if(l==r)return ;int mid=l+r>>1;
		(p<=mid)?ins(lc[u],l,mid,p,x):ins(rc[u],mid+1,r,p,x);
	}
	inline int query(int u,int l,int r,int ql,int qr,int lim){
		if(!u||mn[u]>lim)return 0;if(l==r)return l;int mid=l+r>>1;
		if(qr<=mid)return query(lc[u],l,mid,ql,qr,lim);
		if(mid<ql)return query(rc[u],mid+1,r,ql,qr,lim);
		int res=0;
		if(rc[u]&&mn[rc[u]]<=lim)res=query(rc[u],mid+1,r,ql,qr,lim);
		if(res)return res;return query(lc[u],l,mid,ql,qr,lim);
	}
	void sub_dfs(int u,int val){
		if(u<=n)ins(rt,1,n,u,u-val+1);
		for(int re v:G[u])sub_dfs(v,val);
	}
	void dfs(int u){
		for(int re p=u;p;p=son[p])
		for(int re v:G[p])if(v!=son[p])dfs(v);
		rt=tot=0;for(int re p=u;p;p=son[p]){
			for(int re v:G[p])if(v!=son[p])sub_dfs(v,len[p]);
			if(p<=n)ins(rt,1,n,p,p-len[p]+1);
			for(int re id:q[p])
			ans[id]=std::max(ans[id],
				query(1,1,n,Ql[id],Qr[id]-1,Ql[id])-Ql[id]+1);
		}
	}
}

signed main(){
#ifdef zxyoi
	freopen("border.in","r",stdin);
#endif
	scanf("%s",s+1);n=strlen(s+1);SAM::init();
	for(int re i=1;i<=n;++i)SAM::push_back(i,s[i]-'a');
	SAM::radix_sort();tree_dissection();Q=gi();
	for(int re i=1;i<=Q;++i){
		Ql[i]=gi(),Qr[i]=gi();
		addq(i,Qr[i]);
	}SGT1::work(),SGT2::dfs(n+1);
	for(int re i=1;i<=Q;++i)cout<<ans[i]<<"\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值