找回密码
 立即注册
查看: 4|回复: 0

力扣2846 边权重均等查询 从LCA到路径处理的深度解析

[复制链接]
  • 打卡等级:即来则安
  • 打卡总天数:16
  • 打卡月天数:7
  • 打卡总奖励:111
  • 最近打卡:2025-07-12 15:59:47

21

主题

1

回帖

2469

积分

VIP会员

积分
2469
发表于 昨天 15:18 | 显示全部楼层 |阅读模式
一、问题重述
给定一棵n个节点的树,每个边有一个权重值。需要处理多个查询,每个查询给出两个节点u和v,要求将u到v路径上所有边的权重变成相同值的最小操作次数(每次操作可将某边权重+1或-1)。
二、解题思路
核心算法采用LCA(**公共祖先)+路径统计:
  • 预处理每个节点的深度和父节点信息
  • 使用二进制提升法快速查询LCA
  • 统计路径上各权重的出现频次
  • 计算将所有边变为众数的最小操作次数

三、完整C++代码
  1. #include <vector>
  2. #include <algorithm>
  3. using namespACe std;

  4. class Solution {
  5.     vector<vector<pair<int, int>>> adj; // 邻接表:{节点, 边权}
  6.     vector<vector<int>> parent;         // 二进制提升父节点表
  7.     vector<int> depth;                  // 节点深度
  8.     vector<array<int, 27>> cnt;         // 节点到根的各权重计数
  9.    
  10. public:
  11.     vector<int> minOperationsQueries(int n, vector<vector<int>>& edges, vector<vector<int>>& queries) {
  12.         // 初始化
  13.         adj.resize(n);
  14.         parent.assign(n, vector<int>(20, -1));
  15.         depth.resize(n);
  16.         cnt.resize(n);
  17.         for(auto& e : edges) {
  18.             adj[e[0]].emplace_back(e[1], e[2]);
  19.             adj[e[1]].emplace_back(e[0], e[2]);
  20.         }
  21.         
  22.         // DFS预处理
  23.         dfs(0, -1, 0);
  24.         
  25.         // 二进制提升预处理
  26.         for(int k = 1; k < 20; ++k) {
  27.             for(int u = 0; u < n; ++u) {
  28.                 if(parent[u][k-1] != -1) {
  29.                     parent[u][k] = parent[parent[u][k-1]][k-1];
  30.                 }
  31.             }
  32.         }
  33.         
  34.         // 处理查询
  35.         vector<int> res;
  36.         for(auto& q : queries) {
  37.             int u = q[0], v = q[1];
  38.             int l = lca(u, v);
  39.             
  40.             // 合并路径上的权重计数
  41.             array<int, 27> total{};
  42.             for(int i = 1; i <= 26; ++i) {
  43.                 total[i] = cnt[u][i] + cnt[v][i] - 2 * cnt[l][i];
  44.             }
  45.             
  46.             // 计算操作次数
  47.             int sum = 0, max_cnt = 0;
  48.             for(int i = 1; i <= 26; ++i) {
  49.                 sum += total[i];
  50.                 max_cnt = max(max_cnt, total[i]);
  51.             }
  52.             res.push_back(sum - max_cnt);
  53.         }
  54.         return res;
  55.     }
  56.    
  57. private:
  58.     void dfs(int u, int p, int d) {
  59.         parent[u][0] = p;
  60.         depth[u] = d;
  61.         if(p != -1) {
  62.             // 继承父节点的计数
  63.             cnt[u] = cnt[p];
  64.             // 更新当前边的权重计数
  65.             for(auto& [v, w] : adj[u]) {
  66.                 if(v == p) {
  67.                     cnt[u][w]++;
  68.                     break;
  69.                 }
  70.             }
  71.         }
  72.         // 递归处理子节点
  73.         for(auto& [v, w] : adj[u]) {
  74.             if(v != p) dfs(v, u, d+1);
  75.         }
  76.     }
  77.    
  78.     int lca(int u, int v) {
  79.         // 确保u是较深的节点
  80.         if(depth[u] < depth[v]) swap(u, v);
  81.         // 提升u到与v同深度
  82.         for(int k = 19; k >= 0; --k) {
  83.             if(depth[u] - (1 << k) >= depth[v]) {
  84.                 u = parent[u][k];
  85.             }
  86.         }
  87.         if(u == v) return u;
  88.         // 同时提升u和v
  89.         for(int k = 19; k >= 0; --k) {
  90.             if(parent[u][k] != -1 && parent[u][k] != parent[v][k]) {
  91.                 u = parent[u][k];
  92.                 v = parent[v][k];
  93.             }
  94.         }
  95.         return parent[u][0];
  96.     }
  97. };
复制代码


四、关键算法详解1. 数据结构设计
  • 邻接表adj存储树的边关系,每个元素是pair<邻接节点, 边权>
  • parent数组:二进制提升表,parent[k]表示节点u向上2^k步的祖先
  • cnt数组:记录从根到每个节点路径上各权重出现的次数

2. DFS预处理
通过深度优先搜索完成三件事:
  • 记录每个节点的直接父节点
  • 计算节点深度
  • 统计根到当前节点路径上的权重分布

3. 二进制提升法
预处理每个节点向上2^k步的祖先,将LCA查询复杂度从O(n)优化到O(logn)。核心思想是利用二进制拆分,将线性查找转化为对数级别的跳跃查询。
4. 查询处理流程
对于每个查询u-v:
  • 找到它们的LCA节点l
  • 计算路径u-l-v上的权重分布
  • 找出出现次数最多的权重(众数)
  • 计算将所有边变为众数的最小操作次数

五、复杂度分析
  • 预处理:O(nlog n)时间,O(nlog n)空间
  • 查询:O(log n + 26)时间(26是权重的可能取值范围)
  • 总复杂度:O(nlog n + q(log n + 26)),其中q是查询次数

来源:竞赛资料

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表