第一种能够高效找到公共祖先节点,但缺少了一些功能,第二种更加完备
using ll = long long;
using i64 = long long;
struct LCA {
int n;
std::vector<int> siz, top, dep, parent;
//邻接表
std::vector<std::vector<int>> adj;
int cur;
HLD() {}
HLD(int n) {
init(n);
}
void init(int n) {
this->n = n;
siz.resize(n);
top.resize(n);
dep.resize(n);
parent.resize(n);
adj.assign(n, {});
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void work(int root = 0) {
//根节点的根节点为根节点
top[root] = root;
//深度为0
dep[root] = 0;
//父节点为-1
parent[root] = -1;
//父节点
dfs1(root);
dfs2(root);
}
//这一步操作完后子节点的adj中没有父节点,
void dfs1(int u) {
//当前节点不是根节点
if (parent[u] != -1) {
//删除当前节点中的父节点,不能往回遍历
adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));
}
//大小为1
siz[u] = 1;
for (auto& v : adj[u]) {
//v为当前节点的子节点
//他们的父节点为u这是肯定的
parent[v] = u;
//深度为父节点的+1
dep[v] = dep[u] + 1;
dfs1(v);
//大小加上v
siz[u] += siz[v];
//总是将子节点多的放在u的第一个
if (siz[v] > siz[adj[u][0]]) {
std::swap(v, adj[u][0]);
}
}
}
void dfs2(int u) {
for (auto v : adj[u]) {
//子节点最多的点top为root,其它都为它本身
top[v] = v == adj[u][0] ? top[u] : v;
dfs2(v);
}
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) {
u = parent[top[u]];
}
else {
v = parent[top[v]];
}
}
return dep[u] < dep[v] ? u : v;
}
int dist(int u, int v) {
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
};
第二种可以在两点路径之间求最值,这是第一种不具备的
struct LCA {
std::vector<int>vis, dep;
std::vector<std::vector<int>>fa, cos;
std::vector<std::vector<std::pair<int,int>>>g;
LCA(int n){
init(n+1);
}
void init(int n) {
vis.resize(n);
dep.resize(n);
fa.assign(n, std::vector<int>(21));
cos.assign(n, std::vector<int>(21));
g.resize(n);
}
void add(int u, int v, int w = 0) {
g[u].push_back({ v,w });
g[v].push_back({ u,w });
}
void dfs(int r, int p) {
vis[r] = 1;
fa[r][0] = p;
dep[r] = dep[p] + 1;
for (int j = 1; j <= 20; j++) {
int f = fa[r][j - 1];
if (f > 0) {
fa[r][j] = fa[f][j - 1];
cos[r][j] = std::min(cos[r][j - 1], cos[f][j - 1]);
}
}
for (auto t : g[r]) {
int y = t.first,w = t.second;
if (y != p) {
cos[y][0] = w;
dfs(y, r);
}
}
}
int lca(int x, int y) {
if (dep[x] > dep[y])std::swap(x, y);
int t = dep[y] - dep[x];
int ans = 1e9;
for (int j = 0; t > 0; j++, t >>= 1) {
if (t & 1) {
ans = std::min(ans, cos[y][j]);
y = fa[y][j];
}
}
if (x == y)return x;
for (int j = 20; j >= 0; j--) {
if (fa[x][j] != fa[y][j]) {
ans = std::min(ans, std::min(cos[x][j], cos[y][j]));
x = fa[x][j], y = fa[y][j];
}
}
ans = std::min(ans, std::min(cos[x][0], cos[y][0]));
return fa[x][0];
}
int dist(int u, int v) {
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
};
关于这个lca方法,当前代码里我return的是x和fa[x][0],这是它们的父节点,如果要返回最值,就把x和fa[x][0]改成ans即可,如果要求路径最大值就把所有min改成max,这个倍增算法的lca要比第一个树剖的lca慢不少.
这里有一道例题[NOIP2013 提高组] 货车运输 - 洛谷
最大生成树上求lca
#define v(t) std::vector<t>
#define pii std::pair<int,int>
struct LCA {
v(int)vis, dep;
v(v(int))fa, cos;
v(v(pii))g;
LCA(int n) {
init(n + 1);
}
void init(int n) {
vis.resize(n);
dep.resize(n);
fa.assign(n, v(int)(21));
cos.assign(n, v(int)(21));
g.resize(n);
}
void add(int u, int v, int w = 0) {
g[u].push_back({ v,w });
g[v].push_back({ u,w });
}
//当前节点,父节点,p=0表示r没有父节点
void dfs(int r, int p) {
vis[r] = 1;//遍历过设为1
fa[r][0] = p;//父节点设为p
dep[r] = dep[p] + 1;//父节点深度+1
for (int j = 1; j <= 20; j++) {//预处理该节点
int f = fa[r][j - 1];//一开始是fa[r][0]
if (f > 0) {
fa[r][j] = fa[f][j - 1];//fa[i][j]表示距离当前节点j层的节点
cos[r][j] = std::min(cos[r][j - 1], cos[f][j - 1]);//那么cos[r][j]可以由上一层的最小
}
}
for (auto t : g[r]) {
int y = t.first, w = t.second;
if (y != p) {
//将其到父节点r的边权值设为w
cos[y][0] = w;
dfs(y, r);
}
}
}
int lca(int x, int y) {
//让y节点为深度更大的节点
if (dep[x] > dep[y])std::swap(x, y);
int t = dep[y] - dep[x];//交换完后dep[y]就是更大的那个
int ans = 1e9;
for (int j = 0; t > 0; j++, t >>= 1) {
//t表示两者深度之差,当t为奇数
if (t & 1) {
ans = std::min(ans, cos[y][j]);
y = fa[y][j];
}
}
if (x == y)return ans;
for (int j = 20; j >= 0; j--) {
if (fa[x][j] != fa[y][j]) {
ans = std::min(ans, std::min(cos[x][j], cos[y][j]));
x = fa[x][j], y = fa[y][j];
}
}
ans = std::min(ans, std::min(cos[x][0], cos[y][0]));
return ans;
}
};
struct DSU {
std::vector<int>f, siz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
std::iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y)return false;
siz[x] += siz[y];
f[y] = x;
return true;
}
};
struct edge {
int u, v, w;
bool operator<(const edge& e)const {
return w > e.w;
}
};
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int n, m, q;
std::cin >> n >> m;
DSU dsu(n + 1);
LCA lca(n);
v(edge)a(m);
for (int i = 0; i < m; i++) {
int u, v, w;
std::cin >> u >> v >> w;
a[i] = { u,v,w };
}
std::sort(a.begin(), a.end());
for (int i = 0; i < m; i++) {
edge t = a[i];
int u = t.u, v = t.v, w = t.w;
if (dsu.merge(u, v)) {
lca.add(u, v, w);
}
}
//上面不用管,求kruskal的
for (int i = 1; i <= n; i++) {
//1到n,没遍历过的,不可避免有多个树
if (!lca.vis[i]) lca.dfs(i, 0);
}
std::cin >> q;
while (q--) {
int x, y;
std::cin >> x >> y;
if (!dsu.same(x, y)) std::cout << "-1\n"; //如果不连通,直接返回-1
else std::cout << lca.lca(x, y) << "\n";
}
return 0;
}