树链剖分求LCA
树链剖分求LCA,首先需要处理以下几个数组:
father[x]:x节点的父亲
son[x]:x节点的重儿子
size[x]:x节点子树的大小(节点个数)
dep[x]:x在树中的深度
top[x]:x所在重链的顶端节点
树链剖分需要两次dfs,第一次求出father、son、size和dep数组,第二次求出top数组。然后再求lca即可。
附上代码:
#include <iostream> #include <cstdio> #define maxn 1000050 using namespace std; int n,m,s; int head[maxn],cnt; int fa[maxn],dep[maxn],siz[maxn],son[maxn]; int top[maxn]; struct EDGE { int next; int to; }edge[maxn]; void add(int u,int v) { edge[++cnt].next=head[u]; edge[cnt].to=v; head[u]=cnt; } void dfs_1(int root) { siz[root]=1; son[root]=0; for(int i=head[root];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[root])continue; dep[v]=dep[root]+1; fa[v]=root; dfs_1(v); if(siz[son[root]]<siz[v]) son[root]=v; siz[root]+=siz[v]; } } void dfs_2(int root,int tp) { top[root]=tp; if(son[root]) dfs_2(son[root],tp); for(int i=head[root];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[root]||v==son[root])continue; dfs_2(v,v); } } int lca(int u,int v) { int fu=top[u],fv=top[v]; while(fu!=fv) { if(dep[fu]<dep[fv]) { swap(fu,fv); swap(u,v); } u=fa[fu]; fu=top[u]; } if(dep[u]<dep[v])swap(u,v); return v; } int main() { scanf("%d%d%d",&n,&m,&s); dep[s]=1; for(int i=1;i<=n-1;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs_1(s); dfs_2(s,s); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); printf("%d\n",lca(u,v)); } return 0; }
代码注释版(用于理解):
#include <iostream> #include <cstdio> #define maxn 1000050 using namespace std; int n,m,s; int head[maxn],cnt; int fa[maxn],dep[maxn],siz[maxn],son[maxn];//fa[i]表示i结点父亲,dep[i]表示i结点深度,siz[i]表示i结点子树大小(子树结点数) //son[i]表示i的重儿子 int top[maxn];//top[i]表示i结点所在重链的顶部节点。叶节点的top是自己。 struct EDGE { int next; int to; }edge[maxn]; void add(int u,int v) { edge[++cnt].next=head[u]; edge[cnt].to=v; head[u]=cnt; }//链式前向星存图 void dfs_1(int root)//root为当前节点 { siz[root]=1;//root的子树大小初始化为1 son[root]=0;//root的重儿子初始化为0 for(int i=head[root];i;i=edge[i].next)//遍历以root为起点的每一条边 { int v=edge[i].to;//v是边的终点 if(v==fa[root])continue;//如果v是父亲,直接跳过 dep[v]=dep[root]+1;//更新v节点深度 fa[v]=root;//更新v节点父亲 dfs_1(v);//继续dfs if(siz[son[root]]<siz[v]) son[root]=v;//找出子树最大的儿子,作为重儿子 siz[root]+=siz[v];//更新子树大小 } }//第一遍dfs,预处理出fa,dep,siz,son数组 void dfs_2(int root,int tp)//tp为当前节点所在重链顶部节点 { top[root]=tp;//更新top if(son[root]) dfs_2(son[root],tp); //如果可以,一直沿着重儿子走 for(int i=head[root];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[root]||v==son[root])continue; //如果遇到父亲或重儿子直接跳过 dfs_2(v,v); //继续更新 } }//预处理top数组 int lca(int u,int v) { int fu=top[u],fv=top[v]; //fu为u节点所在重链的顶部节点,fv同fu while(fu!=fv)//u,v不在一条重链上 { if(dep[fu]<dep[fv]) { swap(fu,fv); swap(u,v); } u=fa[fu]; fu=top[u];//选择深度较大的点往上跳,更新fu数组 } if(dep[u]<dep[v])swap(u,v); return v;//深度较浅的点就是u,v的lca } int main() { scanf("%d%d%d",&n,&m,&s); dep[s]=1; for(int i=1;i<=n-1;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs_1(s); dfs_2(s,s); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); printf("%d\n",lca(u,v)); } return 0; }