首页 > 其他分享 >树的最近公共祖先

树的最近公共祖先

时间:2023-02-07 12:01:03浏览次数:42  
标签:head 祖先 tot int 最近 公共 query id SIZE


LCA(Lowest Common Ancestors),即​​最近公共祖先​​,是指在有根树中,找出某两个结点u和v最近的公共祖先。

推荐书籍:算法进阶指南

倍增算法(在线)

//n结点的树,输出任意两个结点间的最小距离
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int SIZE = 50010;
int f[SIZE][20], d[SIZE], dist[SIZE];
int ver[2 * SIZE], Next[2 * SIZE], edge[2 * SIZE], head[SIZE];
int T, n, m, tot, t;
queue<int> q;

void add(int x, int y, int z) {
ver[++tot] = y; edge[tot] = z; Next[tot] = head[x]; head[x] = tot;
}

// 预处理
void bfs() {
q.push(1); d[1] = 1;
while (q.size()) {
int x = q.front(); q.pop();
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if (d[y]) continue;
d[y] = d[x] + 1;
dist[y] = dist[x] + edge[i];
f[y][0] = x;
for (int j = 1; j <= t; j++)
f[y][j] = f[f[y][j - 1]][j - 1];
q.push(y);
}
}
}

// 回答一个询问
int lca(int x, int y) {
if (d[x] > d[y]) swap(x, y);
for (int i = t; i >= 0; i--)
if (d[f[y][i]] >= d[x]) y = f[y][i];
if (x == y) return x;
for (int i = t; i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}

int main() {
cin >> T;
while (T--) {
cin >> n >> m;
t = (int)(log(n) / log(2)) + 1;
// 清空
for (int i = 1; i <= n; i++) head[i] = d[i] = 0;
tot = 0;
// 读入一棵树
for (int i = 1; i < n; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
bfs();
// 回答问题
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", dist[x] + dist[y] - 2 * dist[lca(x, y)]);
}
}
}

Tarjan离线算法

//n结点的树,输出任意两个结点间的最小距离
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int SIZE = 50010;
int ver[2 * SIZE], Next[2 * SIZE], edge[2 * SIZE], head[SIZE];
int fa[SIZE], d[SIZE], v[SIZE], lca[SIZE], ans[SIZE];
vector<int> query[SIZE], query_id[SIZE];
int T, n, m, tot, t;

void add(int x, int y, int z) {
ver[++tot] = y; edge[tot] = z; Next[tot] = head[x]; head[x] = tot;
}

void add_query(int x, int y, int id) {
query[x].push_back(y), query_id[x].push_back(id);
query[y].push_back(x), query_id[y].push_back(id);
}

int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}

void tarjan(int x) {
v[x] = 1;
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if (v[y]) continue;
d[y] = d[x] + edge[i];
tarjan(y);
fa[y] = x;
}
for (int i = 0; i < query[x].size(); i++) {
int y = query[x][i];
int id = query_id[x][i];
if (v[y] == 2) {
int lca = get(y);
ans[id] = min(ans[id], d[x] + d[y] - 2 * d[lca]);
}
}
v[x] = 2;
}

int main() {
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
head[i] = 0;
query[i].clear(), query_id[i].clear();
fa[i] = i, v[i] = 0;
}
tot = 0;
for (int i = 1; i < n; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
if (x == y) ans[i] = 0;
else {
add_query(x, y, i);
ans[i] = 1 << 30;
}
}
tarjan(1);
for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}
}

DFS + ST在线算法

//在一棵树上,有n个节点,n−1条边,给定两个节点p,q,求它们的最近公共祖先
const int MAXN = 10010;
int rmq[2 * MAXN]; // rmq数组,就是欧拉序列对应的深度序列

struct ST
{
int mm[2 * MAXN];
int dp[2 * MAXN][20]; // 最小值对应的下标
void init(int n)
{
mm[0] = -1;
for (int i = 1; i <= n; i++)
{
mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
dp[i][0] = i;
}
for (int j = 1; j <= mm[n]; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
dp[i][j] = rmq[dp[i][j - 1]] < rmq[dp[i + (1 << (j - 1))][j - 1]] ? dp[i][j - 1] : dp[i + (1 << (j - 1))][j - 1];
}
}
}
int query(int a,int b) // 查询[a,b]之间最小值的下标
{
if (a > b)
{
swap(a, b);
}
int k = mm[b - a + 1];
return rmq[dp[a][k]] <= rmq[dp[b - (1 << k) + 1][k]] ? dp[a][k] : dp[b - (1 << k) + 1][k];
}
};

// 边的结构体定义
struct Edge
{
int to, next;
};

Edge edge[MAXN * 2];

int tot, head[MAXN];
int F[MAXN * 2]; // 欧拉序列,就是dfs遍历的顺序,长度为2*n-1,下标从1开始
int P[MAXN]; // P[i]表示点i在F中第一次出现的位置
int cnt;
ST st;

void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}

void addedge(int u, int v) // 加边,无向边需要加两次
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}

void dfs(int u, int pre, int dep)
{
F[++cnt] = u;
rmq[cnt] = dep;
P[u] = cnt;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == pre)
{
continue;
}
dfs(v, u, dep + 1);
F[++cnt] = u;
rmq[cnt] = dep;
}
}

void LCA_init(int root, int node_num) // 查询LCA前的初始化
{
cnt = 0;
dfs(root, root, 0);
st.init(2 * node_num - 1);
}

int query_lca(int u, int v) // 查询u,v的lca编号
{
return F[st.query(P[u], P[v])];
}

bool flag[MAXN];

int main()
{
int T;
int N;
int u, v;
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
init();
memset(flag, false, sizeof(flag));
for (int i = 1; i < N; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
flag[v] = true;
}
int root;
for (int i = 1; i <= N; i++)
{
if (!flag[i])
{
root = i;
break;
}
}
LCA_init(root, N);
scanf("%d%d", &u, &v);
printf("%d\n", query_lca(u, v));
}
return 0;
}

 

标签:head,祖先,tot,int,最近,公共,query,id,SIZE
From: https://blog.51cto.com/u_14932227/6041863

相关文章