• YbtOJ#532往事之树【广义SAM,线段树合并】


    正题

    题目链接:https://www.ybtoj.com.cn/problem/532


    题目大意

    给出\(n\)个点的一个\(Trie\)树,定义\(S_x\)表示节点\(x\)代表的字符串
    求$$max{|LCP(S_x,S_y)|+|LCS(S_x,S_y)|}(x\neq y)$$
    \(LCP/LCS\)分别表示最长公共前/后缀)

    \(1\leq n\leq 2\times 10^5\)


    解题思路

    正解好像是树上\(SA\)+线段树合并的做法可是我不会,就写了广义\(SAM\)

    \(SAM\)\(parents\)树就是后缀树,这里给出了\(Trie\)树就是一个构造广义\(SAM\)的好条件。
    构造出来的广义\(SAM\)上的\(parents\)树上的\(LCA\)\(len\)就是两个串的\(LCS\)
    \(Trie\)树上的\(LCA\)深度就是两个串的\(LCP\)

    考虑枚举\(Trie\)树上的一个点\(x\),求它的子树中\(LCS\)最大的一个点对,也就是后缀树上\(LCA\)最深。

    对后缀树求一个\(dfs\)序,那么最优的点对都只会出现在相邻的点对中,这样的点对数量不会很多,可以考虑一种枚举的方法。

    可以使用线段树合并,然后合并的时候每个节点维护一个改区间内最左/右的值。然后拿左区间的最右值和右区间的最左值计算答案就好了。

    时间复杂度\(O(n\log^2n)\),如果肯写\(ST\)表求\(LCA\)可以做到\(O(n\log n)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<map>
    using namespace std;
    const int N=4e5+10,T=20,M=N<<5;
    struct node{
    	int to,next,w;
    }a[N];
    int n,tot,cnt,ans,ls[N],len[N],fa[N],f[N][T+1];
    int rfn[N],dfn[N],g[N],rt[N],dep[N],p[N];
    vector<int>G[N];map<int,int>ch[N];
    void addl(int x,int y,int w){
    	a[++tot].to=y;
    	a[tot].next=ls[x];
    	ls[x]=tot;a[tot].w=w;
    	return;
    }
    int Insert(int p,int c){
    	int np=++cnt;len[np]=len[p]+1;
    	for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
    	if(!p)fa[np]=1;
    	else{
    		int q=ch[p][c];
    		if(len[p]+1==len[q])fa[np]=q;
    		else{
    			int nq=++cnt;ch[nq]=ch[q];
    			len[nq]=len[p]+1;fa[nq]=fa[q];
    			fa[np]=fa[q]=nq;
    			for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
    		}
    	}
    	return np;
    }
    void dfs(int x){
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		p[y]=Insert(p[x],a[i].w);
    		dfs(y);
    	}
    	return;
    }
    void dfs2(int x){
    	dfn[++cnt]=x;rfn[x]=cnt;
    	for(int i=0;i<G[x].size();i++){
    		dep[G[x][i]]=dep[x]+1;
    		dfs2(G[x][i]);
    	}
    	return;
    }
    int LCA(int x,int y){
    	x=dfn[x];y=dfn[y];
    	if(dep[x]>dep[y])swap(x,y);
    	for(int i=T;i>=0;i--)
    		if(dep[f[y][i]]>=dep[x])
    			y=f[y][i];
    	if(x==y)return x;
    	for(int i=T;i>=0;i--)
    		if(f[x][i]!=f[y][i])
    			x=f[x][i],y=f[y][i];
    	return f[x][0];
    }
    struct SegTree{
    	int cnt,ls[M],rs[M],l[M],r[M];
    	void Change(int &x,int L,int R,int pos){
    		if(!x)x=++cnt;l[x]=r[x]=pos;
    		if(L==R)return;
    		int mid=(L+R)>>1;
    		if(pos<=mid)Change(ls[x],L,mid,pos);
    		else Change(rs[x],mid+1,R,pos);
    		return;
    	}
    	int Merge(int &x,int y,int L,int R){
    		if(!x||!y){x=x|y;return 0;}
    		int mid=(L+R)>>1,ans=0;
    		ans=Merge(ls[x],ls[y],L,mid);
    		ans=max(ans,Merge(rs[x],rs[y],mid+1,R));
    		l[x]=ls[x]?l[ls[x]]:l[rs[x]];
    		r[x]=rs[x]?r[rs[x]]:r[ls[x]];
    		if(ls[x]&&rs[x])ans=max(ans,len[LCA(r[ls[x]],l[rs[x]])]);
    		return ans;
    	}
    }Tr;
    void dfs3(int x,int dep){
    	Tr.Change(rt[x],1,cnt,rfn[p[x]]);
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		dfs3(y,dep+1);
    		g[x]=max(g[x],g[y]);
    		g[x]=max(g[x],Tr.Merge(rt[x],rt[y],1,cnt));
    	}
    	ans=max(ans,g[x]+dep);
    	return;
    }
    int main()
    {
    	freopen("recollection.in","r",stdin);
    	freopen("recollection.out","w",stdout);
    	scanf("%d",&n);
    	for(int i=2;i<=n;i++){
    		int x,w;
    		scanf("%d%d",&x,&w);
    		addl(x,i,w);
    	} 
    	cnt=p[1]=1;dfs(1);
    	for(int i=2;i<=cnt;i++)
    		G[fa[i]].push_back(i),f[i][0]=fa[i];
    	cnt=0;dfs2(1);
    	for(int j=1;j<=T;j++)
    		for(int i=1;i<=cnt;i++)
    			f[i][j]=f[f[i][j-1]][j-1];
    	dfs3(1,0);
    	printf("%d\n",ans);
    }
    
  • 相关阅读:
    读取xml文件(可执行文件根目录debug)
    c# winform textbox与combox让用户不能输入
    枚举类型
    值类型与引用类型
    error: failed to push some refs to 'https://git.oschina.net/bluede/TuShuGuanLi.g it'
    left join on 和where中条件的放置位置
    left join、right join、inner join、full join
    Union、Union All、Intersect、Minus
    分层设计的好处
    Hibernate查询方式
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/14412660.html
Copyright © 2020-2023  润新知