最近公共祖先(LCA)
倍增法
1、通过一个倍增数组来向根节点预处理出以每个顶点不同深度的值,然后在通过两点之间的深度差值化为等深度,最后得到最近公共祖先的节点,也可得到两点的最短路径值。
2、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(nlogn)\)。
3、模版:洛谷P3379。
4、倍增算法求 \(lca\) :(也可以用来求解两点之间最短路径上的 \(rmq\))等等。
template<typename T>
struct bei_lca{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
static const int MX = 21;
int n, m, cnt, root;
vector<int> dep, head;
vector<node> edg;
vector<array<int, MX>> fa;// 不同距离的祖先
vector<array<T, MX>> cost;// 该路径边权值之和
bei_lca() {}
bei_lca(int n_, int m_, int root_ = 1) : dep(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1), fa(n_ + 1), cost(n_ + 1) {
cnt = 0;
n = n_;
m = m_;
root = root_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_, int root_ = 1) {
cnt = 0;
n = n_;
m = m_;
root = root_;
dep.assign(n_ + 1, 0);
head.assign(n_ + 1, 0);
edg.assign(m_ << 1 | 1, node());
fa.assign(n_ + 1, array<int, MX>());
cost.assign(n_ + 1, array<T, MX>());
}
inline void dfs(int u, int pa) {
fa[u][0] = pa;
dep[u] = dep[pa] + 1;
for (int i = 1; i < MX; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
cost[u][i] = cost[fa[u][i - 1]][i - 1] + cost[u][i - 1];
}
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
cost[v][0] = w;
dfs(v, u);
}
}
// inline T lca(int x, int y) {// 查询两个点的最短路径
// if (dep[x] < dep[y]) {
// swap(x, y);
// }
// int depth = dep[x] - dep[y];
// T ans = 0;
// for (int i = 0; depth; ++i, depth >>= 1) {
// if (depth & 1) {
// ans += cost[x][i];
// x = fa[x][i];
// }
// }
// for (int i = MX; i >= 0; i--) {
// if (fa[x][i] != fa[y][i]) {
// ans += cost[x][i] + cost[y][i];
// x = fa[x][i];
// y = fa[y][i];
// }
// }
// if (x != y) {
// ans += cost[x][0] + cost[y][0];
// x = fa[x][0];
// y = fa[y][0];// 公共祖先
// }
// return ans;
// }
inline int lca(int x, int y) {// 查找两个点的最近公共祖先
if (dep[x] < dep[y]) {
swap(x, y);
}
int depth = dep[x] - dep[y];
for (int i = 0; depth; ++i, depth >>= 1) {
if (depth & 1) {
x = fa[x][i];
}
}
for (int i = MX - 1; i >= 0; i--) {
if (fa[x][i] != fa[y][i]) {
x = fa[x][i];
y = fa[y][i];
}
}
if (x != y) {
x = fa[x][0];
y = fa[y][0];// 公共祖先
}
return x;
}
};
void solve() {
int n, m, s;
std::cin >> n >> m >> s;
bei_lca<i64> tr(n, n - 1, s);
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
tr.add(u, v, 1);
tr.add(v, u, 1);
}
tr.dfs(s, 0);
for (int i = 1; i <= m; i++) {
int x, y;
std::cin >> x >> y;
std::cout << tr.lca(x, y) << '\n';
}
}
注:这里的所求的最短路是针对于边权来实现的。
欧拉序列
1、通过欧拉序列(也叫 \(dfn\) 序列)的特殊性质,像 \(ST\) 表一样预处理出一些有必要的区间(同样也是基于倍增算法实现的),这里需要维护两个数组,一个代表这个区间内的最小深度,一个维护的是这个最小深度中的节点编号,这样就可以得到两个节点的最近公共祖先了。
2、如图:
3、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(nlogn)\)。
4、模版:洛谷P3379。
int lg[N + 1];
void init() {
for (int i = 2; i <= N; i++) {
lg[i] = lg[i >> 1] + 1;
}
}
template<typename T>
struct Oula_lca{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
static const int MX = 20;
int n, m, cnt, tot, root;
vector<int> dep, dfn, pos, head;
vector<node> edg;
vector<array<int, MX>> depmn, rev;
Oula_lca() {}
Oula_lca(int n_, int m_, int root_) : dep(n_ << 1 | 1), dfn(n_ << 1 | 1), pos(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1), depmn(n_ << 1 | 1), rev(n_ << 1 | 1) {
n = n_;
m = m_;
cnt = 0;
tot = 0;
root = root_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_, int root_ = 1) {
n = n_;
m = m_;
cnt = 0;
tot = 0;
root = root_;
dep.assign(n_ << 1 | 1, 0);
dfn.assign(n_ << 1 | 1, 0);
pos.assign(n_ + 1, 0);
head.assign(n_ + 1, 0);
edg.assign(m_ << 1 | 1, node());
depmn.assign(n_ << 1 | 1, array<int, MX>());
rev.assign(n_ << 1 | 1, array<int, MX>());
}
inline void dfs(int u, int depth) {
pos[u] = ++tot;
dfn[tot] = u;
dep[tot] = depth;
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
i64 w = edg[i].w;
if (pos[v]) {
continue;
}
dfs(v, depth + 1);
dfn[++tot] = u;
dep[tot] = depth;
}
}
inline void deal() {// 预处理
dfs(root, 0);
for (int i = 1; i <= tot; i++) {
depmn[i][0] = dep[i];
rev[i][0] = dfn[i];
}
for (int i = 1; i <= lg[tot]; i++) {
for (int j = 1; j + (1 << i) <= tot + 1; j++) {
if (depmn[j][i - 1] < depmn[j + (1 << (i - 1))][i - 1]) {
depmn[j][i] = depmn[j][i - 1];
rev[j][i] = rev[j][i - 1];
} else {
depmn[j][i] = depmn[j + (1 << (i - 1))][i - 1];
rev[j][i] = rev[j + (1 << (i - 1))][i - 1];
}
}
}
}
inline int lca(int x, int y) {// 最近公共祖先
if (x == y) return x;
int l = pos[x], r = pos[y];
if (l > r) {
swap(l, r);
}
int le = lg[r - l + 1];
return (depmn[l][le] < depmn[r - (1 << le) + 1][le] ? rev[l][le] : rev[r - (1 << le) + 1][le]);
}
};
void solve() {
int n, m, root;
std::cin >> n >> m >> root;
Oula_lca<int> tr(n, n - 1, root);
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
tr.add(u, v, 1);
tr.add(v, u, 1);
}
tr.deal();
for (int i = 1; i <= m; i++) {
int x, y;
std::cin >> x >> y;
std::cout << tr.lca(x, y) << '\n';
}
}
树链剖分
1、利用重儿子的性质将整个数分为 \(logn\) 块,每块上深度最小的那个点为 \(top\),查询公共祖先时,可以直接通过两个节点 \(x、y\) 的 \(top[x]、top[y]\) 谁的深度大就取哪个的父亲节点,直到找到最后相等为止。
2、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(n)\)。
3、模版:洛谷P3379。
4、树链剖分是我们目前所讲的求最近公共祖先中时间最快的,比其他两个都来得快,它可用于求解树上问题,例如:线段树的维护,这个我们后面的篇章会将。
template<typename T>
struct Sp_lca{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
int n, m, cnt, root;
vector<node> edg;
vector<int> dep, fa, son, siz, top, head;// 深度、父亲节点、重儿子、子树大小、所处在的重链中的最远祖先
Sp_lca() {}
Sp_lca(int n_, int m_, int root_ = 1) : dep(n_ + 1), fa(n_ + 1), son(n_ + 1), siz(n_ + 1), top(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1) {
n = n_;
m = m_;
root = root_;
cnt = 0;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_, int root_ = 1) {
n = n_;
m = m_;
root = root_;
cnt = 0;
dep.assign(n_ + 1, 0);
fa.assign(n_ + 1, 0);
son.assign(n_ + 1, 0);
siz.assign(n_ + 1, 0);
top.assign(n_ + 1, 0);
head.assign(n_ + 1, 0);
edg.assign(m_ << 1 | 1, node());
}
inline void dfs1(int u, int pa) {
fa[u] = pa;
siz[u] = 1;
dep[u] = dep[pa] + 1;
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) {
son[u] = v;
}
}
}
inline void dfs2(int u, int topx) {
top[u] = topx;
if (son[u]) {
dfs2(son[u], topx);
} else {
return;
}
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == fa[u] || v == son[u]) {
continue;
}
dfs2(v, v);
}
}
inline void deal() {
dfs1(root, 0);
dfs2(root, root);
}
inline int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) {
swap(x, y);
}
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
};
void solve() {
int n, m, root;
std::cin >> n >> m >> root;
Sp_lca<int> tr(n, n - 1, root);
for (int i = 1, u, v; i < n; i++) {
std::cin >> u >> v;
tr.add(u, v, 1);
tr.add(v, u, 1);
}
tr.deal();
for (int i = 1; i <= m; i++) {
int x, y;
std::cin >> x >> y;
cout << tr.lca(x, y) << '\n';
}
}
四毛子算法
日后更新 dog

浙公网安备 33010602011771号