欧拉序
前置知识
欧拉序
欧拉序所做的,就是在访问节点时记录一遍,每次回溯时再记录一遍。
正片
\(u\) 和 \(v\) 两节点的 LCA 就是欧拉序中 \(u\) 第一次出现的位置和 \(v\) 第一次出现的位置之间的所有节点中深度最浅的点。
证明显然,从 \(u\) 到 \(v\) 会走过 \(lca(u,v)\) 但不会走过 \(lca(u,v)\) 的祖先,因此显然。
代码?没写。
复杂度为 \(O(n\log_2{n}+m)\),但由于欧拉序长度为 \(2n-1\),会有 \(2\) 倍的常数。
dfn 序
前置知识
dfn 序
dfn,即 dfs number,顾名思义,是指在用 dfs 遍历树中每个节点的顺序给节点编的号
如图,该树可能的一种 dfn 序如下图
st 表
见 (还没写)
正片
定义 \(dfn_u\) 为节点 \(u\) 的 dfn 序,\(dep_u\) 为节点 \(u\) 的深度,\(fa_u\) 为节点 \(u\) 的父节点,\(rdfn_u\) 为对应 dfn 序的节点。
那么,当 \(u\ne v\) 且 \(dfn_u<dfn_v\) 时,\(u\) 和 \(v\) 的 LCA 为 dfn 序为 \(dfn_u+1\) 到 \(dfn_v\) 的所有节点中深度最浅的点的父节点。
怎么证明呢,我们分类讨论一下。
当 \(u\) 为 \(v\) 祖先时,显然。
否则,我们画个图
其中,dfn 序为 \(dfn_u+1\) 到 \(dfn_v\) 的所有节点为下图中圈出来的点(多圈了 \(3\) 和 \(u\),\(v\) 写成 \(y\) 了)
此时显然。
复杂度呢?\(O(n\log_2{n}+m)\),由于 \(dfn\) 序长度为 \(n\),因此相比欧拉序常数小 \(\frac{1}{2}\)。
code
你很期待这个
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N=5e5+10;
int lg[N],st[N][20],dfn[N],rdfn[N],fa[N],dep[N];
struct edge{
int to,nxt;
}g[N<<1];
int head[N],tot;
void add(int u,int v){
g[tot].to=v;
g[tot].nxt=head[u];
head[u]=tot;
tot++;
return;
}
int cnt;
void dfs(int u){
dfn[u]=++cnt;
rdfn[cnt]=u;
dep[u]=dep[fa[u]]+1;
for(int i=head[u];~i;i=g[i].nxt){
int v=g[i].to;
if(!dfn[v]){fa[v]=u;dfs(v);}
}
return;
}
int main(){
cin.tie(0);
ios::sync_with_stdio(false);
memset(head,-1,sizeof(head));
int n,m,s;
cin>>n>>m>>s;
for(int i=1,x,y;i<n;i++){
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(s);
for(int i=1;i<=n;i++){
st[i][0]=rdfn[i];
}
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int j=1;j<=lg[n];j++){
for(int i=1;i<=n-(1<<j)+1;i++){
int x=st[i][j-1],y=st[i+(1<<(j-1))][j-1];
if(dep[x]<dep[y]){
st[i][j]=x;
}else{
st[i][j]=y;
}
}
}
while(m--){
int u,v;
cin>>u>>v;
if(u==v){
cout<<u<<"\n";
continue;
}
int l=min(dfn[u],dfn[v])+1,r=max(dfn[u],dfn[v]);
int j=lg[r-l+1];
int x=st[l][j],y=st[r-(1<<j)+1][j];
cout<<(dep[x]<dep[y]?fa[x]:fa[y])<<"\n";
}
return 0;
}