Codevs_P2370 小机房的树(LCA)

本文介绍了一种使用LCA算法解决特定场景下寻找两节点间最小能量消耗路径的问题。通过深度优先搜索预处理节点深度及跳跃父节点,实现快速查询最低公共祖先及其对应的代价。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述 Description
小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力

输入描述 Input Description
第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
输出描述 Output Description
一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。

样例输入 Sample Input
3
1 0 1
2 0 1
3
1 0
2 0
1 2

样例输出 Sample Output
1
1
2

数据范围及提示 Data Size & Hint
1<=n<=50000, 1<=m<=75000, 0<=c<=1000

思路:很裸的LCA,但比较麻烦,先DFS计算深度,在处理它2^i的数据,需要两个数组存跳2^i步的位置及权值,存算跳2^i步的时候忘记加上前面走过的了,调了半天qwq
PS:出题人脑洞有点大23333333

#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
#define N 50005
int n,m;
int deep[N];
int f[N][31],t[N][31];
struct g{
    vector<int> q;
    vector<int> v;
}w[N];
void dfs(int x,int d){
    deep[x]=d;
    for(int i=0;i<w[x].q.size();i++)
        if(!deep[w[x].q[i]]){
            dfs(w[x].q[i],d+1);
            f[w[x].q[i]][0]=x;
            t[w[x].q[i]][0]=w[x].v[i];
        }
}
void work(){
    for(int i=1;i<=30;i++)
        for(int j=0;j<n;j++){
            f[j][i]=f[f[j][i-1]][i-1];
            t[j][i]=t[j][i-1]+t[f[j][i-1]][i-1];
        }
}
int lca(int u,int v){
    if(deep[u]<deep[v]) swap(u,v);
    int l=deep[u]-deep[v],vv=0;
    if(l)
        for(int i=0;i<=30;i++){
            if((1<<i)&l) vv+=t[u][i],u=f[u][i];
        }
    if(u==v) return vv;
    for(int i=30;i>=0;i--){
        if(f[u][i]!=f[v][i]){
            vv+=t[u][i]+t[v][i];
            u=f[u][i];v=f[v][i];
        }
    }
    vv+=t[u][0]+t[v][0];
    return vv;
}
int main(){
    scanf("%d",&n);int u,v,c;
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&u,&v,&c);
        w[u].q.push_back(v);w[u].v.push_back(c);
        w[v].q.push_back(u);w[v].v.push_back(c);
    }
    dfs(0,1);work();
    scanf("%d",&m);
    while(m--){
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }
}
<think>嗯,用户想了解如何在C++中实现图的添加边操作和计算最低公共祖先LCA)。首先,我需要回忆一下图的结构和相关算法。 关于添加边,通常图可以用邻接表或邻接矩阵表示。邻接表更节省空间,适合稀疏图。所以,用户可能需要用邻接表来存储图,然后通过vector的push_back方法添加边。例如,对于无向图,添加边(u, v)时,需要在u的邻接表中添加v,同时v的邻接表中添加u。如果是树结构,可能只需要单向边,比如子节点指向父节点或者反过来,具体看LCA的实现方式。 接下来是LCA的实现。LCA的算法有多种,比如暴力法、倍增法、Tarjan算法等。对于静态树结构,倍增法预处理时间O(n log n),每次查询O(log n),比较高效。用户可能需要一个具体的实现示例。 首先,构建树结构。通常用邻接表存储,每个节点保存它的子节点或者父节点。然后预处理每个节点的深度和祖先信息。倍增法中,每个节点需要存储不同距离的祖先,比如第2^k层的祖先。预处理时,通过DFS遍历,记录每个节点的深度和直接父节点(即倍增表的第0层),然后填充更高层的祖先信息。 在LCA查询时,需要先将两个节点调整到同一深度,然后同时向上跳跃,直到找到共同的祖先。具体步骤是:首先将较深的节点上移,直到两者深度相同;然后同时从最大的跳跃步长开始尝试,逐步缩小步长,直到找到LCA。 然后考虑代码实现。定义结构体或类来表示,包含邻接表、深度数组、祖先表等。添加边的方法就是将子节点加入父节点的邻接表中,并记录父节点信息。预处理阶段进DFS或BFS来填充深度和祖先表。 可能需要注意的根节点确定。通常假设根节点是某个特定的节点,比如0或者1,具体根据问题而定。在代码中需要明确根节点,或者在构造函数中指定。 测试部分,用户可能需要创建实例,添加边,然后查询LCA。例如,构建一棵,添加边(0,1), (0,2), (1,3), (1,4)等,然后查询节点3和4的LCA是否为1。 另外,关于时间复杂度,预处理是O(n log n),每次查询是O(log n),空间复杂度是O(n log n)。需要确保邻接表构建正确,避免循环或错误的父子关系。 用户提到的“push_back”应该是指用vector的push_back方法来添加边到邻接表中。例如,邻接表是一个vector<vector<int>>,每个节点的邻接节点列表用vector存储,添加边时push_back对应的节点。 可能还需要考虑的遍历顺序,比如DFS或BFS,以确保每个节点的深度和祖先信息正确填充。DFS更适合预处理,因为可以递归处理子节点。 总结步骤: 1. 构建的邻接表结构,添加边的函数。 2. 预处理每个节点的深度和倍增祖先表。 3. 实现LCA查询函数,调整节点深度,然后共同上移找祖先。 现在需要将这些思路转化为具体的C++代码,并确保符合用户需求。同时需要处理可能的边界情况,比如空,单节点,或者查询的两个节点相同的情况。</think>### 图的邻接表构建LCA实现 #### 1. 图的邻接表构建(添加边) 使用`vector<vector<int>>`存储邻接表,无向图需双向添加: ```cpp #include <vector> using namespace std; class Tree { private: int n; // 节点总数 vector<vector<int>> adj; // 邻接表 int root; // 根节点标识 // LCA相关预处理数据 vector<int> depth; vector<vector<int>> up; // up[u][k]表示u的第2^k级祖先 public: Tree(int nodes, int root_node = 0) : n(nodes), root(root_node) { adj.resize(n); depth.resize(n, 0); up.resize(n, vector<int>(20, -1)); // 假设log2(n) < 20 } // 添加无向边(双向) void addEdgeUndirected(int u, int v) { adj[u].push_back(v); adj[v].push_back(u); } // 添加有向边(用于树结构) void addEdgeDirected(int parent, int child) { adj[parent].push_back(child); up[child][0] = parent; // 直接父节点 } }; ``` #### 2. LCA预处理(倍增法) 通过DFS预处理每个节点的层级和祖先信息: ```cpp class Tree { // ...(接上述代码) void preprocess() { dfs(root, -1); // 从根节点开始DFS // 填充倍增表 for (int k = 1; k < 20; k++) { for (int u = 0; u < n; u++) { if (up[u][k-1] != -1) { up[u][k] = up[up[u][k-1]][k-1]; } } } } void dfs(int u, int parent) { for (int v : adj[u]) { if (v != parent) { // 避免回环 depth[v] = depth[u] + 1; up[v][0] = u; // 直接父节点 dfs(v, u); } } } }; ``` #### 3. LCA查询实现 通过倍增法实现$O(\log n)$查询: ```cpp class Tree { // ...(接上述代码) int lca(int u, int v) { // 确保u更深,否则交换 if (depth[u] < depth[v]) swap(u, v); // 将u提升到v同一深度 for (int k = 19; k >= 0; k--) { if (depth[u] - (1 << k) >= depth[v]) { u = up[u][k]; } } // 如果此时u == v,直接返回 if (u == v) return u; // 同时向上跳跃 for (int k = 19; k >= 0; k--) { if (up[u][k] != up[v][k]) { u = up[u][k]; v = up[v][k]; } } return up[u][0]; } }; ``` #### 4. 使用示例 ```cpp int main() { Tree t(5, 0); // 5个节点,根为0 t.addEdgeDirected(0, 1); // 0 -> 1 t.addEdgeDirected(0, 2); // 0 -> 2 t.addEdgeDirected(1, 3); // 1 -> 3 t.addEdgeDirected(1, 4); // 1 -> 4 t.preprocess(); // 必须调用预处理 cout << t.lca(3, 4) << endl; // 输出1 cout << t.lca(3, 2) << endl; // 输出0 return 0; } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值