LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度 LCT + SAM + 线段树

我们有一个经典模型:

两个串的最长公共后缀长度,是后缀树中两点 LCA 的深度. 

直接求 LCA 似乎有些困难,不妨这样想 : 

设两个串在后缀树中对应的点分别为 $a,b$,将 $a$ 到根的路径涂色,$b$ 向根爬,遇到的第一个涂色点即为 $a$ 与 $b$ 的 LCA.  

我们用 $LCT$ 来维护 这颗树,涂色操作直接 $Access$ 并区间赋值. 

此时树的形态是怎样的 ? 

$a$ 点到根的路径是一个重链,$b$ 向上爬肯定会爬到 $a$ 所在的重链上. 

直接爬肯定会很慢,不妨直接 $Access(b)$ ? 

考虑 $Access$ 时的语句 : $y=x,x=f_{x}$.  

我们在 $Access(b)$ 过程中碰到的第一个被涂色的点其实就是我们要求的 LCA.  

尤其当被涂色的点变多的时候,每次 $Access$ 的复杂度就是均摊 $log_{2}{n}$ 的了. 

是不是十分神奇 ?

观察这部分代码: 

void Access(int x,int co)
{ int t=0; while(x) { splay(x); if(pos[x]) tr::update(1,n,1,pos[x],dis[x]); pos[x]=co, rson=t, t=x,x=f[x]; } }

我们一边 $Access$ ,一边将该询问点向上爬,每次遇到一个新的重链就将重链染成当前颜色(区间中的下标).可能会好奇为什么只更新那个下标更小的呢 ? 

因为如果更新下标大的,而不更新下标小的,会出现这种情况: 

当前询问区间左端点大于下标小的那个,那么显然下标大的继承不了下标小的的贡献. 

为了避免这种情况,也就是说希望贡献给对未来没有影响的那个,我们只更新给下标更小的那个. (如果询问左端点小于下标小的点的话一定是能覆盖到的)

而下标大的点如何处理呢 ? 

既然不对答案做贡献,我们将遇到的每一个重链都染成更大的下标. (显然我们以后要染的下标肯定都会大于该下标,所以肯定下标越大越好). 

最难的部分处理完毕了,查询的时候直接在对应的线段树里查一个区间最大值即可.  

#include<bits/stdc++.h>
#define maxn 200003 
using namespace std;
void setIO(string s)
{
	string in=s+".in", out=s+".out"; 
	freopen(in.c_str(),"r",stdin); 
	// freopen(out.c_str(),"w",stdout); 
}
int n,Q;  
int dis[maxn],track[maxn];   
char str[maxn];    
namespace tr
{
	int maxv[maxn<<2]; 
	void update(int l,int r,int x,int k,int p)
	{
		if(l==r)
		{
			maxv[x]=p; 
			return; 
		}
		int mid=(l+r)>>1;
		if(k<=mid) update(l,mid,(x<<1),k,p); 
		else update(mid+1,r,(x<<1)|1,k,p); 
		maxv[x]=max(maxv[x<<1],maxv[(x<<1)|1]); 
	}
	int query(int l,int r,int x,int L,int R)
	{
		if(l>=L&&r<=R) return maxv[x]; 
		int mid=(l+r)>>1,tmp=0;
		if(L<=mid) tmp=max(tmp,query(l,mid,x<<1,L,R)); 
		if(R>mid) tmp=max(tmp,query(mid+1,r,(x<<1)|1,L,R)); 
		return tmp;             
	}
}; 
namespace tree 
{
	#define lson ch[x][0]
	#define rson ch[x][1] 
	int f[maxn],ch[maxn][30],pos[maxn],sta[maxn];    
	int get(int x)
	{
		return ch[f[x]][1]==x; 
	}
	int isrt(int x)
	{
		return !(ch[f[x]][0]==x||ch[f[x]][1]==x); 
	}  
	void pushdown(int x)
	{
		if(!x)return;       
		if(pos[x]) 
		{
			if(lson) pos[lson]=pos[x]; 
			if(rson) pos[rson]=pos[x]; 
		}
	}
	void rotate(int x)
	{
		int old=f[x], fold=f[old],which=get(x); 
		if(!isrt(old)) ch[fold][ch[fold][1]==old]=x;   
		ch[old][which]=ch[x][which^1],f[ch[old][which]]=old; 
		ch[x][which^1]=old,f[old]=x,f[x]=fold;    
	}
	void splay(int x)
	{
		int u=x,v=0,fa; 
		sta[++v]=u; 
		while(!isrt(u)) sta[++v]=f[u],u=f[u]; 
		while(v) pushdown(sta[v--]); 
		for(u=f[u];(fa=f[x])!=u;rotate(x))  
			if(f[fa]!=u) 
				rotate(get(fa)==get(x)?fa:x); 
	} 
	void Access(int x,int co)
	{ 
		int t=0;
		while(x)
		{
			splay(x);  
			if(pos[x]) tr::update(1,n,1,pos[x],dis[x]);   
			pos[x]=co, rson=t, t=x,x=f[x];    
		}
	}
}; 
namespace SAM
{
	int last,tot;
	int ch[maxn][30],f[maxn];   
	void init()
	{
		last=tot=1; 
	}
	void ins(int c,int o)
	{
		int np=++tot,p=last; 
		last=np,dis[np]=dis[p]+1;   
		while(p&&!ch[p][c]) ch[p][c]=np,p=f[p]; 
		if(!p) f[np]=1; 
		else 
		{
			int q=ch[p][c]; 
			if(dis[q]==dis[p]+1) f[np]=q; 
			else 
			{
				int nq=++tot; 
				dis[nq]=dis[p]+1; 
				memcpy(ch[nq],ch[q],sizeof(ch[q])); 
				f[nq]=f[q], f[np]=f[q]=nq; 
				while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p];    // 割裂操作    
			}
		}        
		track[o]=np;    
	}
}; 
int answer[maxn]; 
struct OPT
{
	int l,r,id;  
}opt[maxn]; 
bool cmp(OPT a,OPT b)
{
	return a.r<b.r; 
}
int main()
{
	// setIO("input"); 
	SAM::init(); 
	scanf("%d%d",&n,&Q);
	scanf("%s",str+1); 
	for(int i=1;i<=n;++i) SAM::ins(str[i]-'0',i); 
	for(int i=2;i<=SAM::tot;++i) tree::f[i]=SAM::f[i]; 
	for(int i=1;i<=Q;++i) scanf("%d%d",&opt[i].l,&opt[i].r),opt[i].id=i; 
	sort(opt+1,opt+1+Q,cmp); 
    for(int i=1,j=1;i<=n;++i)
    {
    	tree::Access(track[i], i);             // 打上 i 点的标记              
    	while(opt[j].r==i && j<=Q) 
    	{ 
    		answer[opt[j].id]=tr::query(1,n,1,opt[j].l,opt[j].r);    
    		++j; 
    	}
    }
    for(int i=1;i<=Q;++i) printf("%d\n",answer[i]); 
	return 0; 
}

  

posted @ 2019-06-15 10:21  EM-LGH  阅读(256)  评论(0编辑  收藏  举报