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

1|0正题

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


1|1题目大意

给出n个点的一个Trie树,定义Sx表示节点x代表的字符串
max|LCP(Sx,Sy)|+|LCS(Sx,Sy)|(xy)
LCP/LCS分别表示最长公共前/后缀)

1n2×105


1|2解题思路

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

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

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

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

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

时间复杂度O(nlog2n),如果肯写ST表求LCA可以做到O(nlogn)


1|3code

#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); }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14412660.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(65)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示