事情的相似度(SAM+LCT+树状数组)

 

 

题解

蒟蒻的第一道字符串大题

此题的题意是求编号为一段区间的前缀的最长公共后缀的长度

而这个最长公共后缀是可以超过这个区间限制的(被坑了好久。。。)

 

那么这题就比较有思路了

我们可以考虑一下暴力

首先,我们对于每一个前缀[1,i],算出所有的前缀[1,1~i-1]与它的最长公共后缀的大小,存到一个vector里面

查询的时候就只查[l,r]中每个点x的vector里面的[l,x-1]的答案,取最大值即可

这样做是O(Qn^2)的

考虑最长公共后缀的本质,不难联想到SAM

先对原串建出SAM,在SAM上,两个点在fail树上的LCA的len值表示了这两个点的最长公共后缀的长度

一个前缀的所有后缀表示在fail树上就是该前缀的对应点x到根节点的一条链

再开一下脑洞,联想到[LNOI2014]LCA那道题

考虑离线询问,对于右端点 r 处理出所有左端点 l 的答案

我们把所有的1~r-1的前缀对应点x到根的路径依次做一个标记覆盖

(这其实是在贪心,因为越靠后的前缀越可能影响更多的查询)

然后我们查询:在前缀r的对应点xr到根的路径上,覆盖时间大于等于 l 的len值最大的点

直接做这个问题会非常麻烦,因为max不能前缀相减,你还需要用二维线段树来可持久化树链剖分

然而这并没有发挥我们离线的优势

考虑再维护一个树状数组,来记录当前r对应的每个左端点的答案

利用LCT来维护链覆盖与标记的下传,在Access的过程中更新树状数组

每次都只更新跳链时跳向的那一个点的权值

这时有人就会问,万一中间有一个点的位置更靠后,而这种做法没有更新到这个点怎么办?

其实你Access之前,你到根需要跳多少次链,你就经过了多少种不同的链标记

如图,链3在Access的时候,会经过两种不同的标记,两种标记都会被更新到

所以不用担心漏掉某个点没更新的情况

注意,标记下传之后不要清零,因为标记刚打上去会被splay前的pdpath传到子树中

下一次直接访问到该点的时候就没有标记来更新了,这样可以减小常数

如果觉得难以理解,可以在pushdown的时候更新树状数组,这样做应该是可以清零标记的,只不过常数会比较大

代码:O(nlog^2n)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define N 200005
int n,m;
int pos[N],ans[N];
namespace SAM{
	int ch[N][2],fa[N],len[N],las,tot;
	void extend(int x){
		int p,np,q,nq;
		p=las;np=++tot;
		len[np]=len[p]+1;
		for(;p&&!ch[p][x];p=fa[p])
			ch[p][x]=np;
		if(!p)fa[np]=1;
		else{
			q=ch[p][x];
			if(len[q]==len[p]+1)fa[np]=q;
			else{
				nq=++tot;
				len[nq]=len[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];
				for(;p&&ch[p][x]==q;p=fa[p])ch[p][x]=nq;
				fa[q]=fa[np]=nq;
			}
		}
		las=np;
	}
}//----SAM----
int tra[N];
void upd(int x,int k)
{
	x=n-x+1;
	while(x<=n){
		tra[x]=max(tra[x],k);
		x+=(x&-x);
	}
}
int qry(int x)
{
	int ret=0;x=n-x+1;
	while(x){
		ret=max(ret,tra[x]);
		x-=(x&-x);
	}
	return ret;
}
namespace LCT{
	int ch[N][2],fa[N],la[N];
	bool pdc(int x){return ch[fa[x]][1]==x;}
	bool nrt(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
	void rot(int x){
		int y=fa[x],z=fa[y];bool flg=pdc(x);
		if(nrt(y))ch[z][pdc(y)]=x;// !!!
		if(ch[y][flg]=ch[x][flg^1])
			fa[ch[y][flg]]=y;
		ch[x][flg^1]=y;
		fa[y]=x;fa[x]=z;
	}
	void pushdown(int x){
		if(la[x]){
			la[ch[x][0]]=la[ch[x][1]]=la[x];
			//la[x]=0;  // !!!
		}
	}
	void pdpath(int x){if(nrt(x))pdpath(fa[x]);pushdown(x);}
	void splay(int x){
		for(pdpath(x);nrt(x);rot(x))
			if(nrt(fa[x]))rot(pdc(fa[x])==pdc(x)?fa[x]:x);
	}
	void acc(int x,int k){
		for(int i=0;x;x=fa[x]){
			splay(x);
			if(la[x])upd(la[x],SAM::len[x]);
			la[x]=k;
			ch[x][1]=i;
			i=x;
		}
	}
}//----LCT----
vector<pair<int,int> > G[N];
int main()
{
	//freopen("A1.in","r",stdin);
	SAM::tot=SAM::las=1;
	int i,x,l,r;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%1d",&x);SAM::extend(x);
		pos[i]=SAM::las;
	}
	for(i=1;i<=SAM::tot;i++)LCT::fa[i]=SAM::fa[i];
	for(i=1;i<=m;i++){
		scanf("%d%d",&l,&r);
		G[r].push_back(make_pair(l,i));
	}
	for(i=1;i<=n;i++){
		LCT::acc(pos[i],i);
		for(int j=0;j<(int)G[i].size();j++)
			ans[G[i][j].second]=qry(G[i][j].first);
	}
	for(i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值