当前位置: 首页 > news >正文

P6374 「StOI-1」树上询问(倍增+LCA)

题目链接

题目难度

不难,是蓝题。

题目解法概括

主要考察LCA的本质,然后运用倍增优化求解LCA之类的问题。

思路

lca(a, b) 可以理解为 节点 a 到 节点 b 的简单路径上的转折点,LCA 是有根树上的定义,有根树上的父子关系唯一,所以 a 到 b 的简单路径上的转折点唯一。

那么对于无根树,只有当节点 c 在 a 到 b 的简单路径上,树变成有根树后,c 才有可能成为 a 到 b 的路径上的转折点,而成为 lca(a, b),所以 c 在 a 到 b 的简单路径上,结果大于0,否则结果为0。

通过这一本质,可以明确结果是否为0,现在聚焦到如何求出结果大于 0 情况下结果具体的值。

首先 a 到 b 的简单路径上 c 以外的节点不能为根,不然这个节点就会是 lca(a, b)(也就是 a 到 b 的简单路径上的转折点),a(或 b)能通向的且通向的路径不经过 c 的节点也不能为根,否则 a(或 b)就为 lca(a, b)。

但是真按规律这么实现,时间复杂度太高,不允许。

对于无根树,第一步是指定根节点,转化为有根树,且整个过程根节点不变,不然调整根节点后,又要重新获取树的信息,太花时间,指定根的目的也是为了方便处理,所有的事都有最终目的。

这道题也可以先固定根,那么规律就变成了:

  • 若 c = lca(a, b),则结果为 n - nums[jump(a, c)] - nums[jump(b, c)](nums[u] = 以节点 u 为根的子树的节点个数,jump(u, v) = 节点 u 向上跳到节点 v 的下一层所在的节点),如此剔除了简单路径上 c 以外的节点及 a(或 b)能通向的且通向的路径不经过 c 的节点。
  • 若 c 为 a 到 lca(a, b) 路径上的节点(即 c = lca(a, c) 且 depth[c] > depth[lca(a, b)]),则结果为 nums[c] - nums[jump(a, c)],nums[c] 不包含节点 b 能通向的且通向的路径不经过 c 的节点及部分 a 到 b 的简单路径上的节点,nums[jump(a, c)] 包含了节点 a 能通向的且通向的路径不经过 c 的节点及部分 a 到 b 的简单路径上的节点,根据容斥原理,可得出答案。
  • 若 c 为 b 到 lca(a, b) 路径上的节点(即 c = lca(b, c) 且 depth[c] > depth[lca(a, b)]),则结果为 nums[c] - nums[jump(b, c)]。

分类讨论完毕,变成有根树后,c 在 a 到 b 的简单路径上的情况就这几种。

实现

用倍增优化 jump 函数及求 LCA 的过程。

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;typedef long long LL;const LL Maxn = 5 * 1e5 + 5;
const LL Maxk = 20;vector<LL> tree[Maxn];
LL father[Maxn][Maxk], depth[Maxn], nums[Maxn];void dfs(LL u, LL fa) {depth[u] = depth[fa] + 1;father[u][0] = fa;nums[u] = 1;for (LL i = 1; depth[u] > (1 << i); ++i) {father[u][i] = father[father[u][i - 1]][i - 1];}for (auto v : tree[u]) {if (v == fa)  continue;dfs(v, u);nums[u] += nums[v];}return;
}LL LCA(LL u, LL v) {if (depth[u] < depth[v])  swap(u, v);for (LL i = Maxk - 1; i >= 0; --i) {if (depth[father[u][i]] >= depth[v])  u = father[u][i];}if (u == v)  return u;for (LL i = Maxk - 1; i >= 0; --i) {if (father[u][i] != father[v][i]) {u = father[u][i];v = father[v][i];}}return father[u][0];
}LL jump(LL u, LL v) {if (depth[u] == depth[v])  return 0;for (LL i = Maxk - 1; i >= 0; --i) {if (depth[father[u][i]] > depth[v])  u = father[u][i];}return u;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);LL n, q, x, y, a, b, c;cin >> n >> q;for (LL i = 1; i < n; ++i) {cin >> x >> y;tree[x].emplace_back(y);tree[y].emplace_back(x);}dfs(1, 0);LL lca_ab = 0, lca_ac = 0, lca_bc;while (q--) {cin >> a >> b >> c;lca_ab = LCA(a, b), lca_ac = LCA(a, c), lca_bc = LCA(b, c);if (c == lca_ab)  cout << (n - nums[jump(a, c)] - nums[jump(b, c)]) << '\n';else if (depth[c] > depth[lca_ab] && c == lca_ac)  cout << (nums[c] - nums[jump(a, c)]) << '\n';else if (depth[c] > depth[lca_ab] && c == lca_bc) cout << (nums[c] - nums[jump(b, c)]) << '\n';else  cout << 0 << '\n';}return 0;
}

总结

一开始看到这道题没什么思路,一般没什么思路的题需要通过找规律得出答案,可以根据比较小的样例来找规律,首先画出样例,再根据结果和数据的关系发现规律,有的时候直接按规律实现很费劲,那么就需要洞察到等价于规律的易于实现的求解内容。

本篇博客省略了找规律的过程,而是直接说明规律,接着找到等价于规律的易于实现的求解内容。

解决问题的步骤:

  1. 分解问题
  2. 搞清楚每步要求什么,注意要尽量适配算法。
  3. 用有这样特性的算法求得解。

收获

做了这道题,对 LCA 的理解更深刻了。

http://www.dtcms.com/a/438050.html

相关文章:

  • epoll_ctl函数中`sockfd` 和 `ev.data.fd`的疑问解析
  • 做元器件上什么网站做网站公司的排名
  • hadoop-hdfs-secondaryNameNode
  • 每日一个网络知识点:OSI参考模型
  • 怎么在国外网站做推广wordpress企业主题制作视频教程
  • K8s不同工作负载对应LOL里哪位英雄
  • 【探寻C++之旅】第十六章:unordered系列的认识与模拟实现
  • 用terraform 创建一个GKE private cluster
  • [优选算法专题三.二分查找——NO.22寻找峰值]
  • 中国建设银行官方网站下载北京企业做网站
  • [优选算法专题三.二分查找——NO.24搜索旋转排序数组中的最⼩值]
  • 微服务项目->在线oj系统(Java-Spring)--竞赛管理
  • 苏州市吴江太湖新城建设局网站网站模版建设教程
  • 【AI Design】如何利用 Paraflow 从创意到产品设计规范
  • 360免费建站网址是什么深圳网站推广哪家好
  • 【Linux系列】并发世界的基石:透彻理解 Linux 进程 — 进程概念
  • Spring AI alibaba 工具调用
  • 机器学习基础入门(第三篇):监督学习详解与经典算法
  • 做产品的淘宝客网站网站建设的素材处理方式
  • 【专业词典】FAST
  • 诸城网站建设wordpress退出维护
  • 预约记录自动关联功能测试
  • 进程“悄悄话”函数——`socketpair`
  • QT肝8天14--编辑用户
  • Redis Zset的底层秘密:跳表(Skip List)的精妙设计
  • 广州金融网站建设2017网站开发语言排名
  • C++ priority_queue优先级队列
  • Kafka 授权与 ACL 深入实践
  • 西宁市住房和城乡建设局网站做一个个人网站
  • 瑞安做网站多少钱东莞网站建设找谁