[AtCoder abc465_d] X to Y
涉及知识点:图论、树形结构、-叉树、最近公共祖先 (LCA)、双指针、数学(整除与取模)
问题描述
给你两个整数 ,还有一个不小于 的整数 。
有一个变量 ,初始值是 。你可以对 执行以下操作任意次(包括零次):
- 选择一个整数 ,满足 或 ,然后把 的值替换成 。
这里, 表示不大于实数 的最大整数。
请你求出使得 的最少操作次数。题目保证在给定条件下,总能用有限步数让 。
你需要处理 组测试数据,每组独立解答。
约束条件
- 所有输入均为整数。
输入格式
输入从标准输入读取,格式如下:
plain text
T...第 组测试数据 () 格式为:
plain text
X Y K输出格式
请按顺序输出每组测试数据的答案,每个答案占一行。
样例 1
输入样例 1
plain text
411 9 30 0 2842 180 71948706013487601 48019760148910476 89014537输出样例 1
plain text
2075样例说明
看第一组测试数据。
通过下面的操作, 可以在两步内完成:
- 选择 。因为 ,这个选择合法。然后把 的值改成 。
- 选择 。因为 ,这个选择合法。然后把 的值改成 。
不可能用少于两步完成,所以第一行输出 。
正解代码与思路讲解
算法核心思路
本题的本质是一个经典的**树上两点间最短路径(最近公共祖先 LCA)**问题。
如果我们把每个非负整数看作一个节点,由于任何数不断除以 最终都会变成 ,所有的数在“除以 ”的映射下其实构成了一棵以 为根节点的 -叉树。
- 操作 1 ():代表在树上从当前节点向双亲节点移动一步。
- 操作 2 ():代表在树上从当前节点向孩子节点移动一步。
因此,从 变到 的最少操作次数,等价于求在这棵 -叉树中,节点 到节点 的最短路径长度。在树中,两点间的最短路径必然经过它们的最近公共祖先 (LCA)。我们只需分别找出 和 走到根节点 的所有祖先路径,再倒序比对找出它们分叉的位置即可。由于 且 ,树的最大深度不超过 层,所以暴力收集路径并查找 LCA 的单次时间复杂度为 ,效率极高。
正确 AC 代码
cpp
#include <bits/stdc++.h>using namespace std;#define int long long#define endl "\n" void solve() { int x, y, k; cin >> x >> y >> k; vector<int> pathX, pathY; // 1. 顺着树枝向上不断除以 k,收集 X 的所有祖先节点(直至根节点 0) while (true) { pathX.push_back(x); x /= k; if (pathX.back() == 0) break; } // 2. 同理,收集 Y 的所有祖先节点 while (true) { pathY.push_back(y); y /= k; if (pathY.back() == 0) break; } // 3. 使用双指针从两端路径的末尾(即根节点 0)开始倒序向前比对 int i = pathX.size() - 1; int j = pathY.size() - 1; while (i >= 0 && j >= 0) { // 只要发现两个祖先不相同,说明找到了分叉点,此时对应的上一个相同节点就是 LCA if (pathX[i] != pathY[j]) break; i--, j--; } // 4. 此时 pathX[0...i] 和 pathY[0...j] 是独立的分叉路径 // 它们各自到 LCA 的距离之和为 (i + 1) + (j + 1),精简后即为 i + j + 2 cout << i + j + 2 << endl;} signed main() { ios::sync_with_stdio(false); cin.tie(0); int T = 1; cin >> T; while (T--) { solve(); } return 0;}表示抽个倍数的方案数
表示抽k个数为的方案数
有
则