O(1)LCA
大家常用的三种LCA算法如下:
倍增为在线,复杂度 \(\Theta(n\log n)\) 预处理,\(\Theta(\log n)\) 查询。
树剖为在线,复杂度 \(\Theta(n)\) 预处理,\(\Theta(\log n)\) 查询。
Tarjan为离线,\(\Theta(n+q)\) 复杂度。
现在介绍 \(\Theta(n\log n)\) 时间预处理,\(\Theta(1)\) 查询的在线LCA算法。
欧拉序:
其实就是前序遍历时每次回溯也加入序列当中。
如图的欧拉序为
1 2 4 8 4 7 4 2 5 2 1 3 9 3 10 12 10 3 11 3 1
记录每个点第一次在欧拉序中出现的位置\(p_i\),如求LCA(5,10)
\(p_5\) 和 \(p_{10}\) 之间的序列为
5 2 1 3 9 3 10
其中 \(lca(5,10)=1\) 的深度为要求序列中最小的。
其他的点也类似,于是可以用ST表来维护区间深度最小值。
欧拉序的长度为 \(\Theta(n)\) 的,于是预处理复杂度 \(\Theta(n\log n)\),查询复杂度 \(\Theta(1)\)。
代码如下
const int N=500005;
int n,head[N],pedge,dfn[N],lg[N<<1],cnt,id[N<<1],dep[N],st[N<<1][20];
struct Edge{
int to,next;
}edge[N<<1];
void ins(int u,int v){
edge[++pedge]={v,head[u]},head[u]=pedge;
}
void dfs(int u,int father){
dfn[u]=++cnt,id[cnt]=u,dep[u]=dep[father]+1;
for(int e=head[u];e;e=edge[e].next)if(edge[e].to^father)dfs(edge[e].to,u),id[++cnt]=u;
}
void init(){//预处理
dfs(s,0);
lg[0]=-1;
for(int i=1;i<=cnt;i++)st[i][0]=id[i],lg[i]=lg[i>>1]+1;
for(int j=1;j<=19;j++)for(int i=1;cnt-i+1>=1<<j;i++){
int f1=st[i][j-1],f2=st[i+(1<<j-1)][j-1];
st[i][j]=dep[f1]<dep[f2]?f1:f2;
}
}
int lca(int u,int v){//求lca
u=dfn[u],v=dfn[v];
if(u>v)u^=v^=u^=v;
int k=lg[v-u+1],f1=st[u][k],f2=st[v-(1<<k)+1][k];
return dep[f1]<dep[f2]?f1:f2;
}