dfs 序求 lca
这是一个 \(O(n\log n)\) 预处理,\(O(1)\) 回答询问的 lca 算法,比较好写,几乎与树上倍增相当,算是在各个方面都比较优秀的求 lca 的算法。
P3379 【模板】最近公共祖先(LCA)
以这道模板题为例。
设求 lca 的两个点为 \(x,y\)。显然我们应该知道这两个点的 dfs 序。我们先判掉 \(x=y\) 的情况,然后不妨设 \(dfn_x<dfn_y\)。
粗略地说,我们最初的思路是在 \([dfn_x,dfn_y]\) 之间找到有没有什么比较特别的东西。注意到深度是一个比较好考察的东西。设 \(dfn_z\in [dfn_x,dfn_y]\) 且 \(z\) 是 dfs 序在这个区间中的点中最小的那个,显然 \(z\) 的父亲就是 \(x,y\) 的 lca。
于是一个粗略想法是开一个数组然后对于每个位置记录其父亲和深度,然后用 st 表维护这个即可。
但是显然维护起来比较难写,于是我们考虑对于 dfs 序上每个位置,我们直接记录其父亲的 dfs 序,然后直接对这个序列建 st 表求区间最小值即可。然后 lca 就是这个区间最小的 dfs 序对应的点。
然后一种优秀的写法是将 st 表改成从后往前覆盖的,这样可以支持动态在尾部加点使得结构类似于树上倍增。
最后一个问题,如果 \(x,y\) 有祖先关系,那么答案本身就理应是 \(x\)。如果像上面那样做答案会是 \(x\) 的父亲。一个朴素的办法是维护一个 \(siz\) 然后判断 \(y\) 是否在 \(x\) 子树内。但是还有更取巧的方法连 \(siz\) 都不需要维护,就是将查询的区间变为 \((dfn_x,dfn_y]\)。正确性是比较显然的,因为如果 \(x\) 不是 \(y\) 的祖先那么答案显然不变,如果是那么答案就会变成对的。
code
非常简洁。
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
// template<typename P>
// inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
// template<typename Ty,typename ...Args>
// inline void read(Ty &x,Args &...args) {read(x);read(args...);}
// inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=5e5+7;
int n,m,rt,dfncnt,dfn[N],loc[N],f[N][19];
vector<int> q[N];
void dfs(int u,int fa){
dfn[u]=++dfncnt;loc[dfncnt]=u;f[dfn[u]][0]=dfn[fa];
for(int i=1;(1<<i)<=dfn[u];i++)f[dfn[u]][i]=min(f[dfn[u]][i-1],f[dfn[u]-(1<<(i-1))][i-1]); //注意这个 ST 表是从后往前的,方便插入
for(int v:q[u]){if(v==fa)continue;dfs(v,u);}
}
inline int getf(int l,int r){int k=log2(r-l+1);return min(f[r][k],f[l+(1<<k)-1][k]);}
inline int lca(int x,int y){
if(x==y)return x;
if(dfn[x]>dfn[y])swap(x,y);
return loc[getf(dfn[x]+1,dfn[y])];
}
bool Med;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n>>m>>rt;
for(int i=1,u,v;i<n;i++)cin>>u>>v,q[u].push_back(v),q[v].push_back(u);
dfs(rt,0);
for(int i=1;i<=m;i++){
int x,y;cin>>x>>y;cout<<lca(x,y)<<'\n';
}
cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
return 0;
}

浙公网安备 33010602011771号