最近公共祖先 | st 表求 lca 别用欧拉序了!!!

2023.7.17 update

以前写的有点乱,现在简单重写一下核心思想。

第一个思想,我们只要求出 dfn 相邻节点 lca,然后两个点 lca 肯定在他们 dfn 这个区间里相邻节点 lca 中。

然后有个简化代码的发现,dfn 相邻两个节点的 lca,其实是后者的父亲。

然后可以看代码,注意要特判 lca 两个节点相等。

原文

钱菜鸡水平不行,只能写写最近公共祖先了。

目前 OI 所流行的 O(nlogn)O(1) 的 LCA 算法是 欧拉序 + RMQ,显然,欧拉序没有这么好写,而且常数不小(序列长度两倍),所以导致了很多情况下更多人选择了倍增等算法。

欧拉序+RMQ 算法中,我们要实现一个 2n 长度的序列的 RMQ,但是我们发现,我们询问的端点数量却最多只有 n 个,这意味这我们可以将某些连续的段给缩起来。

缩起来后就成了一个序列 A,我们记 idfni 为满足 dfnidfni=i 的一个数,容易发现 Ai=lca(idfni,idfni+1),显然,我们求 x,y(dfnx<=dfny) 的 lca 只需要求出 Ai(dfnxi<dfny)dfn 最小的节点 (注意,需要特判 x=y)。

下面我们的问题是如何求 Ai。可以证明的是 Ai=lca(idfni,idfni+1)=faidfni+1,因此可以在一次 dfs 内简单的求出 A 数组。于是我们只用做一次长度为 n 的 RMQ 预处理,在 O(n)O(1) lca 中也有不错的优化效果。

O(nlogn)O(1)

#include<bits/stdc++.h>
const int maxn = 500500;
int n, m, s;
struct T{ int to, nxt; } way[maxn << 1];
int h[maxn], num;
int st[20][maxn], dfn[maxn], tot;
inline int min(int x,int y){ return dfn[x] < dfn[y] ? x : y; }
inline void link(int x,int y) {
way[++num] = {y, h[x]}, h[x] = num;
way[++num] = {x, h[y]}, h[y] = num;
}
inline void dfs(int x,int fa = 0) {
st[0][tot] = fa, dfn[x] = ++tot;
for(int i = h[x];i;i = way[i].nxt) if(way[i].to != fa)
dfs(way[i].to, x);
}
inline int lca(int x,int y) {
if(dfn[x] > dfn[y]) std::swap(x, y);
const int lg = std::__lg(dfn[y] - dfn[x]);
return x != y ? min(st[lg][dfn[x]], st[lg][dfn[y] - (1 << lg)]) : x;
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0);
std::cin >> n >> m >> s;
for(int i = 1,x,y;i < n;++i)
std::cin >> x >> y, link(x,y);
dfs(s);
for(int i = 1;i < 20;++i) for(int j = 1;j + (1 << i) - 1 < n;++j)
st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
for(int i = 1,x,y;i <= m;++i) {
std::cin >> x >> y;
std::cout << lca(x,y) << '\n';
}
}

O(n)O(1):

#include<bits/stdc++.h>
const int maxn = 1000001;
typedef unsigned u32;
struct istream {
static const int size = 1 << 25;
static const u32 b = 0x30303030;
short map[1 << 16];
char buf[size], *vin;
inline istream() {
for(int i = 0;i < 1 << 16;++i) map[i] = (i >> 12) + (i >> 8 & 15) * 100 + (i >> 4 & 15) * 10 + (i & 15) * 1000;
fread(buf,1,size,stdin);
vin = buf - 1;
}
inline istream& operator >> (int & x) {
x = *++vin & 15, ++ vin;
u32*& idx = (u32*&) vin;
for(;(*idx & b) == b;++idx) x = x * 10000 + map[(*idx ^ *idx >> 12 ^ 13107) & 65535];
for(;isdigit(*vin);++vin) x = x * 10 + (*vin & 15);
return * this;
}
} cin;
struct ostream
{
static const int size = 1 << 23;
char buf[size], *vout;
unsigned map[10000];
inline ostream()
{
for(int i = 0;i < 10000;++i) {
int p = i;
map[i] = p % 10 + 48, p /= 10;
map[i] = map[i] << 8 | p % 10 + 48, p /= 10;
map[i] = map[i] << 8 | p % 10 + 48, p /= 10;
map[i] = map[i] << 8 | p % 10 + 48, p /= 10;
}
vout = buf + size;
}
inline ~ ostream()
{ fwrite(vout,1,buf + size - vout,stdout); }
inline ostream& operator << (int x)
{
for(;x >= 10000;x /= 10000) *--(unsigned*&)vout = map[x % 10000];
do *--vout = x % 10 + 48; while(x /= 10);
return * this;
}
inline ostream& operator << (char x)
{
*--vout = x;
return * this;
}
} cout;
int n,q;
int a[maxn],dfn[maxn],tot;
namespace Rmq{
int st[15][maxn / 32];
int pre[maxn],p[maxn],w[maxn];
inline int min(int x,int y){ return dfn[x] < dfn[y] ? x : y; }
inline void down(int & x,int y){ if(dfn[x] > dfn[y]) x = y; }
inline int qry(int l,int r){
const int lg = std::__lg(r - l);
return l >= r ? 0 : min(st[lg][l],st[lg][r - (1 << lg)]);
}
inline int rmq(int l,int r){
if(l >> 5 == r >> 5) return p[l + __builtin_ctz(w[r] >> l)];
else return min(qry((l >> 5) + 1,r >> 5),min(a[l],pre[r]));
}
inline void build(int n){
++ (n |= 31);
memcpy(p,a,n <<2);
for(int i=0;i<n;i+=32){
static int st[33];
pre[i] = a[i];
int * top = st + 1,s = 1; w[*top = i] = s;
for(int j=i+1;j<i+32;++j){
for(;top != st && dfn[a[j]] < dfn[a[*top]];--top) s ^= 1 << *top;
w[j] = s |= 1 << j; *++top = j; pre[j] = a[st[1]];
}
for(int j=i + 30;j >= i;--j) down(a[j],a[j+1]);
Rmq::st[0][i >> 5] = a[i];
}
for(int i = 1;i < 15;++i)
for(int j = 0;j + (1 << i) - 1 <= n / 32;++j)
st[i][j] = min(st[i - 1][j],st[i - 1][j + (1 << i - 1)]);
}
}
struct T{ int to,nxt; } way[maxn << 1];
int h[maxn],num;
inline void adde(int x,int y){
way[++num] = {y,h[x]}, h[x]=num;
way[++num] = {x,h[y]}, h[y]=num;
}
inline void dfs(int x,int f){
a[tot] = f; dfn[x] = ++tot;
for(int i=h[x];i;i=way[i].nxt) if(way[i].to != f)
dfs(way[i].to,x);
}
inline int lca(int x,int y){
if(dfn[x] > dfn[y]) std::swap(x,y);
return x == y ? x : Rmq::rmq(dfn[x],dfn[y]-1);
}
int ans[maxn];
int main(){
cin >> n >> q;
for(int i = 1,x,y;i < n;++i) cin >> x >> y, adde(x,y);
dfs(1,0); *dfn = 1e9; Rmq::build(n-1);
for(int i = 1,x,y;i <= q;++i) cin >> x >> y, ans[i] = lca(x,y);
for(int i = q;i >= 1;--i) cout << '\n' << ans[i];
}
posted @   skip2004  阅读(6435)  评论(4编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示