LCA——最近公共祖先

前言

其实我卡在了这个知识点很久了qwq。我一直没有找到倍增LCA的方便的写法(处理边界的方法),搞得我画图、手玩了很久找边界,最后才做出来2333

例题

LGOJ P3379 【模板】最近公共祖先(LCA)

暴力LCA

很简单的暴力:先预处理出每个点的深度,再使两个点的深度相等。然后,使两个点一起往它们的父亲跳,直到它们遇到彼此摩擦出爱的火花
时间复杂度: O ( n ) O(n) O(n)

倍增LCA

倍增LCA是一种相当常见、好用的算法
它的大致思想是,采用二进制拆分优化了普通LCA,一次跳好几步,就效率高了
时间复杂度: O ( q ∗ l o g n ) O(q * logn) O(qlogn)


写法1

这种写法不用考虑边界

#define USEFASTERREAD 1

#define rg register
#define inl inline
#define DEBUG printf("qwq\n")
#define DEBUGd(x) printf("var %s is %lld", #x, ll(x))
#define DEBUGf(x) printf("var %s is %llf", #x, double(x))
#define putln putchar('\n')
#define putsp putchar(' ')
#define Rep(a, s, t) for(rg int a = s; a <= t; a++)
#define Repdown(a, t, s) for(rg int a = t; a >= s; a--)
typedef long long ll;
typedef unsigned long long ull;
#include<cstdio>

#if USEFASTERREAD
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
#endif
namespace IO {
	inl void RS() {freopen("test.in", "r", stdin), freopen("test.out", "w", stdout);}
	inl ll read() {
		ll x = 0, f = 1; char ch = getchar();
		for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
		for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
		return x * f;
	}
	inl void write(ll x) {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) write(x / 10);
		putchar(x % 10 + '0');
	}
	inl void writeln(ll x) {write(x), putln;}
	inl void writesp(ll x) {write(x), putsp;}
}
using namespace IO;
template<typename T> inline T Max(const T& x, const T& y) {return y < x ? x : y;}
template<typename T> inline T Min(const T& x, const T& y) {return y < x ? y : x;}
template<typename T> inline void Swap(T& x, T& y) {T tmp = x; x = y; y = tmp;}
template<typename T> inline T Abs(const T& x) {return x < 0 ? -x : x;}
const int MAXN = 5e5 + 5;
const int MAXM = 5e5 + 5;
int N, M, S;
namespace Graph {
    struct Edge {
        int v, nxt;
    }e[MAXM << 1];
    int head[MAXN], cnt;
    void addedge(int u, int v) {
        e[++cnt].v = v;
        e[cnt].nxt = head[u];
        head[u] = cnt;
    }
}
using namespace Graph;
namespace Lca {
    int depth[MAXN];
    int fa[MAXN][21];
    void Dfs(int u, int f) {
        depth[u] = depth[f] + 1;
        fa[u][0] = f;
        for(rg int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
        for(rg int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(v != f) Dfs(v, u);
        }
    }
    int solve(int u, int v) {
        if(depth[u] < depth[v]) Swap(u, v);
        for(rg int i = 20; i >= 0; i--) {
            if(depth[fa[u][i]] >= depth[v]) u = fa[u][i];
            if(u == v) return u;
        }
        for(rg int i = 20; i >= 0; i--) {
            if(fa[u][i] != fa[v][i])
                u = fa[u][i], v = fa[v][i];
        }
        return fa[u][0];
    }
}
using namespace Lca;
int main() {
	//RS();
    N = read(), M = read(), S = read();
    for(rg int i = 1; i < N; i++) {
        int u = read(), v = read();
        addedge(u, v); addedge(v, u);
    }
    Dfs(S, 0);
    for(rg int i = 1; i <= M; i++) {
        int a = read(), b = read();
        writeln(solve(a, b));
    }
	return 0;
}

写法2

这种写法要考虑边界

#define USEFASTERREAD 1

#define rg register
#define inl inline
#define DEBUG printf("qwq\n")
#define DEBUGd(x) printf("var %s is %lld", #x, ll(x))
#define DEBUGf(x) printf("var %s is %llf", #x, double(x))
#define putln putchar('\n')
#define putsp putchar(' ')
#define Rep(a, s, t) for(rg int a = s; a <= t; a++)
#define Repdown(a, t, s) for(rg int a = t; a >= s; a--)
typedef long long ll;
typedef unsigned long long ull;
#include<cstdio>

#if USEFASTERREAD
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
#endif
namespace IO {
	inl void RS() {freopen("test.in", "r", stdin), freopen("test.out", "w", stdout);}
	inl ll read() {
		ll x = 0, f = 1; char ch = getchar();
		for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
		for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
		return x * f;
	}
	inl void write(ll x) {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) write(x / 10);
		putchar(x % 10 + '0');
	}
	inl void writeln(ll x) {write(x), putln;}
	inl void writesp(ll x) {write(x), putsp;}
}
using namespace IO;
template<typename T> inline T Max(const T& x, const T& y) {return y < x ? x : y;}
template<typename T> inline T Min(const T& x, const T& y) {return y < x ? y : x;}
template<typename T> inline void Swap(T& x, T& y) {T tmp = x; x = y; y = tmp;}
template<typename T> inline T Abs(const T& x) {return x < 0 ? -x : x;}
const int MAXN = 5e5 + 5;
const int MAXM = 5e5 + 5;
int N, M, S;
int lg[MAXN];
namespace Graph {
    struct Edge {
        int v, nxt;
    }e[MAXM << 1];
    int head[MAXN], cnt;
    void addedge(int u, int v) {
        e[++cnt].v = v;
        e[cnt].nxt = head[u];
        head[u] = cnt;
    }
}
using namespace Graph;
namespace Lca {
    int depth[MAXN];
    int fa[MAXN][21];
    void Dfs(int u, int f) {
        depth[u] = depth[f] + 1;
        fa[u][0] = f;
        for(rg int i = 1; (1 << i) <= depth[u]; i++)
            fa[u][i] = fa[fa[u][i - 1]][i - 1];
        for(rg int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(v != f) Dfs(v, u);
        }
    }
    int solve(int u, int v) {
        if(depth[u] < depth[v]) Swap(u, v);
        while(depth[u] > depth[v]) {
            u = fa[u][lg[depth[u] - depth[v]]];
        }
        if(u == v) return u;
        for(rg int i = lg[depth[u]]; i >= 0; i--) {
            if(fa[u][i] != fa[v][i])
                u = fa[u][i], v = fa[v][i];
        }
        return fa[u][0];
    }
}
using namespace Lca;
int main() {
	//RS();
    N = read(), M = read(), S = read();
    for(rg int i = 1; i < N; i++) {
        int u = read(), v = read();
        addedge(u, v); addedge(v, u);
    }
    lg[0] = -1;
    for(rg int i = 1; i < MAXN; i++) lg[i] = lg[i >> 1] + 1;
    Dfs(S, 0);
    for(rg int i = 1; i <= M; i++) {
        int a = read(), b = read();
        writeln(solve(a, b));
    }
	return 0;
}

哪种写法更好呢?
我觉得差不多

TarjanLCA

我这道题做了多久鬼知道。。。调代码调了1h,结果错在了并查集mmp

原理请看这篇博客
模板请看这篇博客
大致内容上两篇博客都讲了,我来说说关键点和易错点把
时间复杂度 O ( n + q ) O(n + q) O(n+q)

#define USEFASTERREAD 1

#define rg register
#define inl inline
#define DEBUG printf("qwq\n")
#define DEBUGd(x) printf("var %s is %lld", #x, ll(x))
#define DEBUGf(x) printf("var %s is %llf", #x, double(x))
#define putln putchar('\n')
#define putsp putchar(' ')
#define Rep(a, s, t) for(rg int a = s; a <= t; a++)
#define Repdown(a, t, s) for(rg int a = t; a >= s; a--)
typedef long long ll;
typedef unsigned long long ull;
#include<cstdio>

#if USEFASTERREAD
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
#endif
namespace IO {
	inl void RS() {freopen("test.in", "r", stdin), freopen("test.out", "w", stdout);}
	inl ll read() {
		ll x = 0, f = 1; char ch = getchar();
		for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
		for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
		return x * f;
	}
	inl void write(ll x) {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) write(x / 10);
		putchar(x % 10 + '0');
	}
	inl void writeln(ll x) {write(x), putln;}
	inl void writesp(ll x) {write(x), putsp;}
}
using namespace IO;
template<typename T> inline T Max(const T& x, const T& y) {return y < x ? x : y;}
template<typename T> inline T Min(const T& x, const T& y) {return y < x ? y : x;}
template<typename T> inline void Swap(T& x, T& y) {T tmp = x; x = y; y = tmp;}
template<typename T> inline T Abs(const T& x) {return x < 0 ? -x : x;}
const int MAXN = 500005;
const int MAXM = 500005;
namespace Graph {
    struct Edge {
        int v, nxt;
    }e[MAXN << 1];
    int head[MAXN], cnt;
    void addedge(int u, int v) {
        e[++cnt].v = v;
        e[cnt].nxt = head[u];
        head[u] = cnt;
    }
}
using namespace Graph;
namespace Questions {
    struct Que {
        int v, nxt, id;
        bool del;
    }q[MAXM << 1];
    int qhead[MAXN], qcnt = 1;//开始设为1方便2-3,4-5异或取对
    int ans[MAXM];
    void addque(int u, int v, int id) {
        q[++qcnt].v = v;
        q[qcnt].id = id;
        q[qcnt].del = false;
        q[qcnt].nxt = qhead[u];
        qhead[u] = qcnt;
    }
}
using namespace Questions;
namespace BCSet {
    int fa[MAXN];
    void init(int n) {
        for(rg int i = 1; i <= n; i++) fa[i] = i;
    }
    int getfa(int x) {
        if(x == fa[x]) return x;
        return fa[x] = getfa(fa[x]);
    }
    void mergeto(int x, int y) {//将x加到y上去
        fa[getfa(x)] = getfa(y);
    }
}
using namespace BCSet;
namespace Lca {
    bool vist[MAXN];
    void tarjan(int u, int f) {
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if(v == f || vist[v]) continue;
            tarjan(v, u);
            mergeto(v, u);//very very important!!
            vist[v] = true;
        }
        for(int i = qhead[u]; i; i = q[i].nxt)
            if(!q[i].del && vist[q[i].v]) {
                ans[q[i].id] = getfa(q[i].v);
                q[i].del = q[i ^ 1].del = true;
            }
    }
}
using namespace Lca;
int N, M, S;
int main() {
	//RS();
    N = read(), M = read(), S = read();
    init(N);
    for(rg int i = 1; i < N; i++) {
        int x = read(), y = read();
        addedge(x, y); addedge(y, x);
    }
    for(rg int i = 1; i <= M; i++) {
        int a = read(), b = read();
        addque(a, b, i); addque(b, a, i);
    }
    tarjan(S, 0);
    for(rg int i = 1; i <= M; i++) {
        writeln(ans[i]);
    }
	return 0;
}
  • 看到了那个very very important吗?
    为什么我要说说这个地方呢?一定一定要注意,这里的合并是有方向的。

  • 还有,看到了那个操作吗:
    我们可以使用异或来存取同一个询问的不同的表示内容(如代码)。可以利用
    0 ^ 1 = 1, 1 ^ 1 = 0;
    2 ^ 1 = 3, 3 ^ 1 = 2;
    4 ^ 1 = 5, 5 ^ 1 = 4;
    这样,0-1, 2-3, 3-4, …就是互相对应的。我们可以利用这一点,简化代码。
    同样,这种方法在无向边存图中也用得很多

RMQLCA

这种方法的时间复杂度为 O ( n l o g n + q ) O(nlogn+q) O(nlogn+q),是一种在线做法
方法:使用欧拉路径记录下每个节点,这样的话求LCA就是在时间戳中找到那个在两点的区间内且深度最小的那个点。
可以使用ST表维护

#define USEFASTERREAD 1

#define rg register
#define inl inline
#define DEBUG printf("qwq\n")
#define DEBUGd(x) printf("var %s is %lld", #x, ll(x))
#define DEBUGf(x) printf("var %s is %llf", #x, double(x))
#define putln putchar('\n')
#define putsp putchar(' ')
#define Rep(a, s, t) for(rg int a = s; a <= t; a++)
#define Repdown(a, t, s) for(rg int a = t; a >= s; a--)
typedef long long ll;
typedef unsigned long long ull;
#include<cstdio>

#if USEFASTERREAD
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
#endif
namespace IO {
	inl void RS() {freopen("test.in", "r", stdin), freopen("test.out", "w", stdout);}
	inl ll read() {
		ll x = 0, f = 1; char ch = getchar();
		for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
		for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
		return x * f;
	}
	inl void write(ll x) {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) write(x / 10);
		putchar(x % 10 + '0');
	}
	inl void writeln(ll x) {write(x), putln;}
	inl void writesp(ll x) {write(x), putsp;}
}
using namespace IO;
template<typename T> inline T Max(const T& x, const T& y) {return y < x ? x : y;}
template<typename T> inline T Min(const T& x, const T& y) {return y < x ? y : x;}
template<typename T> inline void Swap(T& x, T& y) {T tmp = x; x = y; y = tmp;}
template<typename T> inline T Abs(const T& x) {return x < 0 ? -x : x;}
const int MAXN = 500005;
const int MAXM = 500005;
int N, M, S;
int lg[MAXN << 1];
namespace Graph {
    struct Edge {
        int v, nxt;
    }e[MAXN << 1];
    int cnt, head[MAXN];
    void addedge(int u, int v) {
        e[++cnt].v = v;
        e[cnt].nxt = head[u];
        head[u] = cnt;
    }
}
using namespace Graph;
namespace Euler {
    int pth[MAXN << 1], pidx;
    int dep[MAXN], pos[MAXN];
    bool vist[MAXN];
    void Dfs(int u, int fa) {
        if(vist[u]) return;
        vist[u] = true;
        dep[u] = dep[fa] + 1;
        pth[++pidx] = u;
        pos[u] = pidx;//记录最初位置
        for(rg int i = head[u]; i; i = e[i].nxt)
            if(!vist[e[i].v] && e[i].v != fa) {
                Dfs(e[i].v, u);
                pth[++pidx] = u;//再记录一个
            }
    }
}
using namespace Euler;
namespace Rmq {
    int st[MAXN << 1][20];
    void init() {
        for(rg int i = 1; i <= pidx; i++) st[i][0] = pth[i];
        for(rg int j = 1; (1 << j) <= pidx; j++)
            for(rg int i = 1; i + (1 << j) - 1 <= pidx; i++) {
                int& s1 = st[i][j - 1];
                int& s2 = st[i + (1 << (j - 1))][j - 1];
                if(dep[s1] < dep[s2])
                    st[i][j] = s1;
                else st[i][j] = s2;
            }
    }
}
using namespace Rmq;
int main() {
	//RS();
    N = read(); M = read(); S = read();
    lg[0] = -1;
    for(rg int i = 1; i < (MAXN << 1); i++) lg[i] = lg[i >> 1] + 1;
    for(rg int i = 1; i < N; i++) {
        int u = read(), v = read();
        addedge(u, v);
        addedge(v, u);
    }
    Dfs(S, 0);
    init();
    for(rg int i = 1; i <= M; i++) {
        int a = read(), b = read();
        if(pos[a] > pos[b]) Swap(a, b);
        int &x = pos[a], &y = pos[b];
        int k = lg[y - x + 1];
        if(dep[st[x][k]] < dep[st[y - (1 << k) + 1][k]])
            writeln(st[x][k]);
        else writeln(st[y - (1 << k) + 1][k]);
    }
	return 0;
}

我才不会告诉你我一直没打init()函数导致调了1h

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值