题解
lca入门:题解 P3379 【【模板】最近公共祖先(LCA)】
那些年我用lca干过的事:
- 建虚树
代码
带常数优化的lca - 1
int Log[N];//log2(x)
//int dfn[N], cnt = 0;//各节点的编号
int fa[N][20];
int dep[N];//树的深度
//常数初始化
void initLog(){
Log[0] = -1, Log[1] = 0;
for (int i = 2; i <= N; ++i) {
Log[i] = Log[i / 2] + 1;
}
}
void dfs(int u, int f) {
//dfn[u] = ++cnt;
dep[u] = dep[f] + 1;
fa[u][0] = f;
for (int i = 1; (1 << i) <= n; ++i) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for (int i = 0; i < e[u].size(); ++i) {
int v = e[u][i];
if (v != f)
dfs(v, u);
}
}
int lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
while (dep[u] > dep[v])
u = fa[u][Log[dep[u] - dep[v]]];
if (u == v) return v;
for (int i = Log[dep[u]]; i >= 0; --i) {
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
/*
使用顺序:
initLog();
dfs(root,0);
int p=lca(u,v);
*/
带常数优化的lca - 2
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m;
int r;//root
int d[N];//分层 depth
int f[N][40];//倍增跳跃 f[u][i]表示u向上跳2^i格的祖先
struct edge {
int next, to;
} e[N * 2];
int head[N], tot = 0;
void add(int x, int y) {
e[++tot] = {head[x], y};
head[x] = tot;
}
int lg[N];
void dfs(int u, int fa) {
d[u] = d[fa] + 1;//记录深度
f[u][0] = fa;//记录父亲
for (int i = 1; i <= lg[d[u]]; ++i) {
f[u][i] = f[f[u][i - 1]][i - 1];
}//直接处理从u往上跳的所有祖先
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (v != fa) dfs(v, u);
}
}
int lca(int x, int y) {
if (d[x] < d[y])swap(x, y);//假设x比y深
while (d[x] > d[y])
x = f[x][lg[d[x] - d[y]] - 1];//逐渐跳到与y同层
if (x == y) return x;//重叠 说明y是x的祖先
for (int i = lg[d[x]] - 1; i >= 0; --i) {//不重合 x和y一起往上跳
if (f[x][i] != f[y][i]) {
//只要父亲不同就跳 父亲相同就停止
x = f[x][i];
y = f[y][i];
//最后一次是没有跳上去的
}
}
return f[x][0];//输出最后一次往上跳
}
int main() {
scanf("%d%d%d", &n, &m, &r);
int x, y;
for (int i = 1; i < n; ++i) {
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
//5e5 最多2^20
for (int i = 1; i <= n; ++i) {//预先算出log2(i)+1的值 常数优化
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
dfs(r, 0);//分层+倍增
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
return 0;
}
不开O2过不了,也可能是头文件的原因?
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, k;
int r;//root
int d[N];//分层
int f[N][40];//倍增跳跃 f[u][i]表示u向上跳i格的祖先
struct edge {
int next, to;
} e[N * 2];
int head[N], tot = 0;
void add(int x, int y) {
e[++tot] = {head[x], y};
head[x] = tot;
}
void dfs(int u) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!d[v]) {//v从未被访问过
d[v] = d[u] + 1;//v是u的儿子
f[v][0] = u;//v向上跳一格一定是x
dfs(v);
}
}
}
int lca(int x, int y) {
if (d[x] < d[y])swap(x, y);//假设x比y深
for (int i = 20; i >= 0; --i) {
if (d[f[x][i]] >= d[y]) {
/*
x往上大跳 跳过头就换小跳
如果仍然比y深 就跳到f[x][i]的位置上
逐渐跳到与y同层
*/
x = f[x][i];
}
}
if (x == y) return x;//重叠 说明y是x的祖先
for (int i = 20; i >= 0; --i) {//不重合 x和y一起往上跳
if (f[x][i] != f[y][i]) {
//只要父亲不同就跳 父亲相同就停止
x = f[x][i];
y = f[y][i];
//最后一次是没有跳上去的
}
}
return f[x][0];//输出最后一次往上跳
}
int main() {
scanf("%d%d%d", &n, &m, &r);
int x, y;
for (int i = 1; i < n; ++i) {
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
d[r] = 1;
f[r][0] = 0;//以r为根
dfs(r);//分层+倍增
//核心代码
for (int i = 1; i <= 20; ++i) {//倍增数组建立
//层层蔓延
for (int u = 1; u <= n; ++u) {
//整理所有的点之间的关系
f[u][i] = f[f[u][i - 1]][i - 1];
//表示第u个点向上跳2^i个点 一定是其向上跳2^(i-1)个点向上跳2^(i-1)所到达的地方
//倍增嘛 2^i=2^(i-1)+2^(i-1)
}
}
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
return 0;
}