• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
最近公共祖先学习

最近公共祖先学习

定义

最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。 为了方便,我们记某点集 \(S=\{v_1,v_2,\ldots,v_n\}\) 的最近公共祖先为\(LCA(v_1, v_2, \dots, v_n)\) 或\(LCA(S)\)。

image-20230418200241272

向上标记法 \(O(N)\)

基本思想就是倍增和二进制拼凑。

\(f[i, j] 表示从 i开始,向上走2^j步所能走到的节点, 0 \le j \le logn\)

\[f(i,j) = father(i), j = 0 \\ f(i,j) = f(f(i, j - 1), j - 1), j \ge 0 \]

\(depth[i] 表示深度\)

​ 哨兵: 如果从\(i\)开始跳\(2^j\)步会跳过根节点,那么\(fa[i, j] = 0\), \(depth[0] = 0\), 并设根节点深度为1, 这时在\(LCA\)函数中我们就能视为不满足条件,继续向低位bit枚举

步骤:

  1. 先将两个点跳到同一层
  2. 让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层

预处理\(O(nlogn)\)

查询\(O(logn)\)

Code

#include <bits/stdc++.h>

using i64 = long long;

const int N = 4e4 + 10, M = N * 2;

int n, m;

int h[N], e[M], ne[M], idx;

int depth[N], fa[N][16];

int q[N];

void add(int a, int b) {
	ne[idx] = h[a], h[a] = idx, e[idx ++] = b;
}

void bfs(int s) {
	memset(depth, 0x3f, sizeof depth);
	depth[0] = 0, depth[s] = 1;
	std::queue<int> q1;
	q1.push(s);

	while (q1.size()) {
		int t = q1.front(); q1.pop();

		for (int i = h[t]; ~i; i = ne[i]) {
			int v = e[i];
			if (depth[v] > depth[t] + 1) {
				q1.push(v);
				depth[v] = depth[t] + 1;	
				fa[v][0] = t;
				for (int k = 1; k <= 15; k ++) {
					fa[v][k] = fa[fa[v][k - 1]][k - 1];
				}
			}
		}
	}
}

int lca(int a, int b) {
	if (depth[a] < depth[b]) std::swap(a, b);

	for (int k = 15; k >= 0; k --) {
		if (depth[fa[a][k]] >= depth[b]) {
			a = fa[a][k];
		}
	}

	if (a == b) return a;

	for (int k = 15; k >= 0; k --) {
		if (fa[a][k] != fa[b][k]) {
			a = fa[a][k];
			b = fa[b][k];
		}
	}

	return fa[a][0];
}

int main() {
	std::cin >> n;

	int root = 0;

	memset(h, -1, sizeof h);

	for (int i = 0; i < n; i ++) {
		int a, b;

		std::cin >> a >> b;

		if (b == -1) root = a;
		else add(a, b), add(b, a);
	}

	bfs(root);

	std::cin >> m;

	while (m --) {
		int a, b;

		std::cin >> a >> b;

		int k = lca(a, b);

		if (k == a) {
			std::cout << 1;
		} else if (k == b) {
			std::cout << 2;
		} else std::cout << 0;

		puts("");
	}
}

Tarjan离线求LCA算法 \(O(n + T)\)

​ 上述复杂度\(n, T\)分别记为节点数和询问数。

​ 在深度优先遍历的时候把点分为三类:

image-20230419192613945

  1. 已经遍历过,且回溯过的点
  2. 正在搜索的分支
  3. 还未搜索到的点

Code

#include <bits/stdc++.h>

using i64 = long long;

const int N = 20010, M = N * 2;

int n, m;

int h[N], e[M], w[M], ne[M], idx;

int p[N], dist[N];

int res[N];

int st[N];

std::vector<std::pair<int, int>> query[N]; // first存查询的另一个点, second存查询编号


void add(int a, int b, int c) {
	ne[idx] = h[a], h[a] = idx, e[idx] = b, w[idx ++] = c;
}

int find(int x) {
	if (x != p[x]) p[x] = find(p[x]);
	return p[x];
}

void dfs(int u, int fa) {
	for (int i = h[u]; ~i; i = ne[i]) {
		int v = e[i];
		if (v == fa) continue;
		dist[v] = dist[u] + w[i];	
		dfs(v, u);
	}		
}

void tarjan(int u) {
	st[u] = 1;

	for (int i = h[u]; ~i; i = ne[i]) {
		int v = e[i];
		if (!st[v]) {
			tarjan(v);
			p[v] = u;
		}
	}

	for (auto item: query[u]) {
		int y = item.first, id = item.second;
		if (st[y] == 2) {
			int anc = find(y);
			res[id] = dist[u] + dist[y] - dist[anc] * 2;
		}
	}

	st[u] = 2;
}

int main() {
	std::cin >> n >> m;

	memset(h, -1, sizeof h);

	for (int i = 0; i < n - 1; i ++) {
		int a, b, c;

		std::cin >> a >> b >> c;

		add(a, b, c), add(b, a, c);
	}

	for (int i = 0; i < m; i ++) {
		int a, b;
		std::cin >> a >> b;

		if (a != b) {
			query[a].push_back({b, i});
			query[b].push_back({a, i});	
		}	
	}

	for (int i = 1; i <= n; i ++) p[i] = i;

	dfs(1, -1);

	tarjan(1);

	for (int i = 0; i < m; i ++) {
		std::cout << res[i] << "\n";
	}
}

基于RMQ的做法

待补

关于离线算法和在线算法

在线算法:对于询问题,每次读入一个询问就输出

离线算法:对于询问题,必须先把所有询问统一读入,在统一计算,然后统一输出

树上查询两个点的距离

​ 由于树无环,两个点间距离只存在一个,并且存在以下等式:

\[dist[x, y] = dist[x] + dist[y] - dist[LCA(x, y)] \]

其中\(dist\)表示该点到根节点间的距离。

posted on 2023-04-19 21:16  Jack404  阅读(20)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3