【2022国赛模拟】白楼剑——SAM、回滚莫队、二次离线

非原题链接

题目描述

请添加图片描述
请添加图片描述

题解

遇到这种找子串出现次数的题,第一时间想到 SAM。

S 2 S_2 S2 建出 SAM 后,我们可以在其 p a r e n t   t r e e \rm parent\,tree parenttree 上做一些预处理,记录一下每个节点的祖先节点答案的最大值。然后在 S 1 S_1 S1 的对应区间内做扫描线,每次找到能够匹配的最长后缀时就可以快速求得它对答案的贡献。

从上面的暴力过程发现,我们能够较快地将询问区间扩大 1,并同时维护答案,而缩小就不行。于是在思考在线或者 P o l y ( log ⁡ n ) Poly(\log n) Poly(logn) 解法未果后,我们可以自然地想到使用回滚莫队。

由于左右两边都要扩展,所以需要建出正串和反串的 SAM。由于回滚莫队往右扩展的起点只有 n \sqrt{n} n 个,所以可以暴力预处理。

往左扩展的话,预处理定位出 S 1 S_1 S1 上每个位置往后能匹配的最长串在 SAM 上的节点后,每次扩展就只需要用一个倍增跳祖先就可以定位到区间内子串对应节点,然后 O ( 1 ) O(1) O(1) 算答案即可。

于是得到一个 O ( n n log ⁡ n ) O(n\sqrt{n}\log n) O(nn logn) 的做法,我的常数显然是过不了的。

考虑把这个 log ⁡ n \log n logn 给优化掉,也就是不用倍增找祖先,而用更快的方法。

一次离线已经走到尽头了,我们可以考虑二次离线。具体做法是,把上面找祖先的请求离线下来挂到 SAM 上,然后在 SAM 上做 DFS,用一个数据结构维护每种长度对应哪个祖先。

分析一下操作数发现,修改次数是 O ( n ) O(n) O(n) 的,查询次数是 O ( n n ) O(n\sqrt{n}) O(nn ) 的,所以再用一个分块 O ( n ) O(\sqrt{n}) O(n ) 修改, O ( 1 ) O(1) O(1) 查询即可。

最终复杂度优化为 O ( n n ) O(n\sqrt{n}) O(nn ),用预先reserve技巧卡常效果更佳。

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define lll __int128
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define END putchar('\n')
#define lowbit(x) ((x)&-(x))
#define inline jzmyyds
using namespace std;
const int MAXN=1e5+3;
const ll INF=1e17;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt>0)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
using pii=pair<int,int>;
char s[MAXN],in[MAXN];
ll qa[33000005],fx[330][MAXN];
int QN,B,BB,n,m,q,pt[MAXN],lt[MAXN],L[MAXN],R[MAXN];
struct itn{int ch[26],fa,len;};
int bf[MAXN],bg[330];
void cov(int L,int R,int d){
	for(int e=(L-1)/BB,l=e*BB+1;l<=m;l+=BB,e++){
		int r=min(m,l+BB-1);
		if(l>R)break;
		if(l>=L&&r<=R)bg[e]=d;
		else for(int i=max(l,L),lm=min(R,r);i<=lm;i++)bf[i]=d;
	}
}
void clr(int L,int R){
	for(int e=(L-1)/BB,l=e*BB+1;l<=m;l+=BB,e++){
		int r=min(m,l+BB-1);
		if(l>R)break;
		if(l>=L&&r<=R)bg[e]=0;
		else for(int i=max(l,L),lm=min(R,r);i<=lm;i++)bf[i]=0;
	}
}
struct SAM{
	itn sam[MAXN<<1];
	int las,tot,t[MAXN<<1],sv[MAXN<<1];
	ll pm[MAXN<<1];
	vector<int>G[MAXN<<1];
	vector<pii>sk[MAXN<<1];
	SAM(){las=tot=1;}
	void samadd(int c){
		int p=las,np=las=++tot;sam[np].len=sam[p].len+1;
		for(;p&&!sam[p].ch[c];p=sam[p].fa)sam[p].ch[c]=np;
		if(!p)sam[np].fa=1;
		else{int q=sam[p].ch[c],nq;
			if(sam[q].len==sam[p].len+1)sam[np].fa=q;
			else{
				nq=++tot,sam[nq]=sam[q],sam[nq].len=sam[p].len+1;
				sam[q].fa=sam[np].fa=nq;
				for(;p&&sam[p].ch[c]==q;p=sam[p].fa)sam[p].ch[c]=nq;
			}
		}t[np]++;
	}
	void pdfs(int x){
		for(int&v:G[x])pdfs(v),t[x]+=t[v];
	}
	void build(){
		for(int i=2;i<=tot;i++)G[sam[i].fa].push_back(i);
		for(int i=1;i<=tot;i++)sk[i].reserve(sv[i]);
		pdfs(1);
	}
	void dfs(int x,ll PM,bool dt){
		pm[x]=PM,PM=max(PM,1ll*sam[x].len*t[x]);
		if(x>1&&dt)cov(sam[sam[x].fa].len+1,sam[x].len,x);
		for(auto&it:sk[x]){
			if(it.fi>=sam[x].len)qa[it.se]=PM;
			else{
				int y=max(bf[it.fi],bg[(it.fi-1)/BB]);
				qa[it.se]=max(1ll*it.fi*t[y],pm[y]);
			}
		}
		for(int&v:G[x])dfs(v,PM,dt);
		if(x>1&&dt)clr(sam[sam[x].fa].len+1,sam[x].len);
	}
}P,Q;
int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	*new(int)=scanf("%s%s",s+1,in+1);
	n=strlen(s+1),m=strlen(in+1),B=ceil(sqrt(n)),BB=ceil(sqrt(m));
	for(int i=1;i<=m;i++)P.samadd(in[i]-'a');
	P.build(),P.dfs(1,0,0);
	for(int l=1,e=0;l<=n;l+=B,e++){
		int p=1,le=0;
		for(int i=l;i<=n;i++){
			int c=s[i]-'a';
			while(p>1&&!P.sam[p].ch[c])p=P.sam[p].fa;
			le=min(le,P.sam[p].len);
			if(P.sam[p].ch[c])p=P.sam[p].ch[c],le++;
			fx[e][i]=max(fx[e][i-1],max(P.pm[p],1ll*le*P.t[p]));
		}
	}
	
	for(int i=m;i>0;i--)Q.samadd(in[i]-'a');
	for(int i=n,le=0,p=1;i>0;i--){
		int c=s[i]-'a';
		while(p>1&&!Q.sam[p].ch[c])p=Q.sam[p].fa;
		le=min(le,Q.sam[p].len);
		if(Q.sam[p].ch[c])p=Q.sam[p].ch[c],le++;
		pt[i]=p,lt[i]=le;
	}
	//由于问题特殊,回滚莫队可以不用排序,直接用预处理的值现算
	q=read();
	for(int i=1;i<=q;i++){
		int l=L[i]=read(),r=R[i]=read(),el=(l-1)/B,er=(r-1)/B;
		if(el==er){for(int j=r;j>=l;j--)Q.sv[pt[j]]++;
		}else for(int j=el*B+B;j>=l;j--)Q.sv[pt[j]]++;
	}Q.build(),QN=0;
	for(int i=1;i<=q;i++){//二次离线
		int l=L[i],r=R[i],el=(l-1)/B,er=(r-1)/B;
		if(el==er){
			for(int j=r;j>=l;j--)
				Q.sk[pt[j]].emplace_back(min(r-j+1,lt[j]),++QN);
		}else{
			for(int j=el*B+B;j>=l;j--)
				Q.sk[pt[j]].emplace_back(min(r-j+1,lt[j]),++QN);
		}
	}Q.dfs(1,0,1),QN=0;
	for(int i=1;i<=q;i++){
		int l=L[i],r=R[i],el=(l-1)/B,er=(r-1)/B;
		ll ans=0;
		if(el==er){
			for(int j=r;j>=l;j--)ans=max(ans,qa[++QN]);
		}else{
			ans=fx[el+1][r];
			for(int j=el*B+B;j>=l;j--)ans=max(ans,qa[++QN]);
		}
		print(ans);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值