zoukankan      html  css  js  c++  java
  • 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
    ",answer[i]); 
    	return 0; 
    }
    

      

  • 相关阅读:
    html5中新增的form表单属性
    FORM
    .Net Core 发布失败
    Sql Server查询最近执行sql
    HttpWebRequest.GetResponse()操作超时
    使用SqlBulkCopy批量插入/更新数据
    ADO .NET 往数据库批量插入数据发生错误:超时时间已到,但是尚未从池中获取链接
    Ueditor代码内容前台只显示一行
    Lambda表达式
    委托的简单使用
  • 原文地址:https://www.cnblogs.com/guangheli/p/11026678.html
Copyright © 2011-2022 走看看