1 条题解
-
1
主要考察树上最短路(倍增法求最近公共祖先)与转化法。
题目大意:给定一棵 n 个节点的树,每条边边权均为 1,其中有 k 个点在 0 的时间内相互可达。q 次询问,求最少经过多长时间可以从 u 走到 v。
子任务一:n,k≤500 且只有一次询问,可以暴力建图(k 个传送点之间连 条边权为 0 的边),并使用双端队列 BFS 解决。
子任务二:不存在传送点,问题变为常规地求树上两点最短路。注意到 n≤2×,使用倍增 LCA 求解即可。
子任务三:我们注意到,从节点 u 到节点 v 可以有两种路径可走:可以不走传送点使用 LCA,也可以选择从传送点走。解决本题目的关键点在于如何快速求得从传送点走到路径长度。
传送过程中消耗的时间为 0,因此只需要计算从 u 到传送点和从传送点到 v 两端路程的长度即可。贪心分析可得,使用的两个传送点一定是分别离 u,v 最近的两个传送点,才能保证消耗的总时间最少。到这里思路已经很明确了:计算离每个点最近的传送点到该节点的距离 dst,最终答案即为 dst+dst。
参考代码:
#include <iostream> #include <vector> #include <queue> using namespace std; const int N = 2e5 + 10; const int lgN = 30; int n, k, s, dep[N], fa[N][lgN], lg[N], dst[N]; vector<int> G[N]; queue<int> Q; void dfs(int u, int fath) { dep[u] = dep[fath] + 1; fa[u][0] = fath; for (int i = 1; (1 << i) <= dep[u]; i ++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for (int i = 0; i < G[u].size(); i ++) { int v = G[u][i]; if (v != fath) { dfs(v, u); } } } int lca(int x, int y) { if (dep[x] < dep[y]) swap(x, y); while (dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]]]; if (x == y) return x; for (int k = lg[dep[x]]; k >= 0; k --) { if (fa[x][k] != fa[y][k]) { x = fa[x][k]; y = fa[y][k]; } } return fa[x][0]; } void bfs() { while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i = 0; i < G[u].size(); i ++) { int v = G[u][i]; if (dst[v] == 1e9) { dst[v] = dst[u] + 1; Q.push(v); } } } } int main() { cin >> n >> k >> s; for (int i = 0; i <= n; i ++) dst[i] = 1e9; for (int i = 1; i < n; i ++) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } lg[1] = 0; for (int i = 2; i <= n; i ++) { lg[i] = lg[i / 2] + 1; } dfs(1, 0); for (int i = 1; i <= k; i ++) { int x; cin >> x; Q.push(x); dst[x] = 0; } bfs(); while (s--) { int u, v; cin >> u >> v; cout << min(dst[u] + dst[v] , dep[u] + dep[v] - 2 * dep[lca(u, v)]) << "\n"; } return 0; }
- 1
信息
- ID
- 646
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 8
- 标签
- 递交数
- 12
- 已通过
- 10
- 上传者