1 条题解
-
0
🔍 核心思路分析
-
问题转化:
- 地图是一棵树( 个点, 条边)。
- 玩家从任意点出发,访问所有有宝藏的点后返回起点,求最短路径。
- 这等价于找到一个包含所有宝藏点的最小连通子图,并求其欧拉回路长度。
-
关键结论:
- 设按照DFS序排序后的宝藏点序列为 。
- 则最短路径长度为:$\sum_{i=1}^{k-1} \text{dist}(a_i, a_{i+1}) + \text{dist}(a_k, a_1)$。
- 这个结论可以理解为:按照DFS序遍历宝藏点,最后返回起点,路径最短。
-
动态维护:
- 使用一个平衡数据结构(如
std::set
)维护当前有宝藏的节点,并按照DFS序排序。 - 当加入或删除一个宝藏点 时:
- 设其在set中的DFS序前驱节点为 ,后继节点为 。
- 则答案的变化量为:$\pm [\text{dist}(x, y) + \text{dist}(x, z) - \text{dist}(y, z)]$。
- 这个公式的含义是:加入 时,我们"断开"了原本 和 的直接路径,改为经过 ,因此增加了 $\text{dist}(x, y) + \text{dist}(x, z) - \text{dist}(y, z)$ 的距离。
- 使用一个平衡数据结构(如
🛠️ 算法实现步骤
-
预处理:
- 进行DFS,计算每个节点的DFS序、深度 和到根节点的距离。
- 预处理LCA(最近公共祖先),使用倍增法或树链剖分,以快速计算任意两点间距离。
-
距离计算:
- $\text{dist}(u, v) = \text{dis}[u] + \text{dis}[v] - 2 \times \text{dis}[\text{LCA}(u, v)]$,其中 表示根节点到 的距离。
-
维护set与答案:
- 初始化一个空的
set
和答案变量ans = 0
。 - 对于每次操作(添加或删除宝藏点 ):
- 找到 在set中的位置,及其前驱 和后继 (注意set是环形的,即首尾相连)。
- 根据操作类型,应用上述公式更新
ans
。 - 更新set。
- 初始化一个空的
📊 复杂度分析
- 预处理:DFS和LCA预处理为 。
- 每次操作:set操作和距离计算为 。
- 总复杂度:,符合题目数据范围要求。
🖥️ 代码实现(C++)
以下是基于上述思路的代码框架(主要参考):
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 1e5 + 5; int n, m; vector<pair<int, ll>> G[MAXN]; // 邻接表存储树 int dfn[MAXN], pos[MAXN], dep[MAXN], fa[MAXN][20], tim; ll dis[MAXN], ans; set<int> st; // 存储有宝藏的节点的dfn bool vis[MAXN]; // 标记节点当前是否有宝藏 void dfs(int u, int f) { dfn[u] = ++tim; pos[tim] = u; dep[u] = dep[f] + 1; fa[u][0] = f; for (int i = 1; i < 20; i++) fa[u][i] = fa[fa[u][i-1]][i-1]; for (auto &e : G[u]) { int v = e.first; ll w = e.second; if (v == f) continue; dis[v] = dis[u] + w; dfs(v, u); } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = 19; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; for (int i = 19; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } ll dist(int u, int v) { return dis[u] + dis[v] - 2 * dis[lca(u, v)]; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i < n; i++) { int u, v; ll w; scanf("%d%d%lld", &u, &v, &w); G[u].push_back({v, w}); G[v].push_back({u, w}); } dfs(1, 0); while (m--) { int t; scanf("%d", &t); if (!vis[t]) { // 插入操作 st.insert(dfn[t]); auto it = st.find(dfn[t]); auto pre = it == st.begin() ? prev(st.end()) : prev(it); auto nxt = next(it) == st.end() ? st.begin() : next(it); int y = pos[*pre], z = pos[*nxt]; ll add_val = dist(t, y) + dist(t, z) - dist(y, z); ans += add_val; vis[t] = true; } else { // 删除操作 auto it = st.find(dfn[t]); auto pre = it == st.begin() ? prev(st.end()) : prev(it); auto nxt = next(it) == st.end() ? st.begin() : next(it); int y = pos[*pre], z = pos[*nxt]; ll del_val = dist(t, y) + dist(t, z) - dist(y, z); ans -= del_val; st.erase(it); vis[t] = false; } printf("%lld\n", ans); } return 0; }
💡 关键点说明
- set的环形处理:当查找前驱或后继时,如果当前节点是set中的第一个或最后一个元素,则需要环形处理,即前驱为最后一个元素,后继为第一个元素。
- 答案初始化:当只有一个宝藏点时,按照公式计算,前驱和后继都是它自己,
dist(t, t) = 0
,所以答案为0,符合题目要求。 - LCA优化:使用倍增法求LCA,保证每次查询的时间复杂度为 。
📝 总结
这道题的关键在于理解DFS序与最短路径的关系,以及如何动态维护序列并快速更新答案。通过将问题转化为维护DFS序上相邻点的距离和,我们可以高效处理每次宝藏点的变动。
-
- 1
信息
- ID
- 3445
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 10
- 已通过
- 1
- 上传者