1 条题解

  • 0
    @ 2025-10-19 19:11:51

    🔍 核心思路分析

    1. 问题转化

      • 地图是一棵树(NN 个点,N1N-1 条边)。
      • 玩家从任意点出发,访问所有有宝藏的点后返回起点,求最短路径。
      • 这等价于找到一个包含所有宝藏点的最小连通子图,并求其欧拉回路长度。
    2. 关键结论

      • 设按照DFS序排序后的宝藏点序列为 a1,a2,,aka_1, a_2, \dots, a_k
      • 则最短路径长度为:$\sum_{i=1}^{k-1} \text{dist}(a_i, a_{i+1}) + \text{dist}(a_k, a_1)$。
      • 这个结论可以理解为:按照DFS序遍历宝藏点,最后返回起点,路径最短。
    3. 动态维护

      • 使用一个平衡数据结构(如 std::set)维护当前有宝藏的节点,并按照DFS序排序。
      • 当加入或删除一个宝藏点 xx 时:
        • 设其在set中的DFS序前驱节点为 yy后继节点为 zz
        • 则答案的变化量为:$\pm [\text{dist}(x, y) + \text{dist}(x, z) - \text{dist}(y, z)]$。
      • 这个公式的含义是:加入 xx 时,我们"断开"了原本 yyzz 的直接路径,改为经过 xx,因此增加了 $\text{dist}(x, y) + \text{dist}(x, z) - \text{dist}(y, z)$ 的距离。

    🛠️ 算法实现步骤

    1. 预处理

      • 进行DFS,计算每个节点的DFS序深度到根节点的距离
      • 预处理LCA(最近公共祖先),使用倍增法或树链剖分,以快速计算任意两点间距离。
    2. 距离计算

      • $\text{dist}(u, v) = \text{dis}[u] + \text{dis}[v] - 2 \times \text{dis}[\text{LCA}(u, v)]$,其中 dis[x]\text{dis}[x] 表示根节点到 xx 的距离。
    3. 维护set与答案

      • 初始化一个空的 set 和答案变量 ans = 0
      • 对于每次操作(添加或删除宝藏点 tt):
        • 找到 tt 在set中的位置,及其前驱 yy 和后继 zz(注意set是环形的,即首尾相连)。
        • 根据操作类型,应用上述公式更新 ans
        • 更新set。

    📊 复杂度分析

    • 预处理:DFS和LCA预处理为 O(NlogN)O(N \log N)
    • 每次操作:set操作和距离计算为 O(logN)O(\log N)
    • 总复杂度O((N+M)logN)O((N + M) \log N),符合题目数据范围要求。

    🖥️ 代码实现(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;
    }
    

    💡 关键点说明

    1. set的环形处理:当查找前驱或后继时,如果当前节点是set中的第一个或最后一个元素,则需要环形处理,即前驱为最后一个元素,后继为第一个元素。
    2. 答案初始化:当只有一个宝藏点时,按照公式计算,前驱和后继都是它自己,dist(t, t) = 0,所以答案为0,符合题目要求。
    3. LCA优化:使用倍增法求LCA,保证每次查询的时间复杂度为 O(logN)O(\log N)

    📝 总结

    这道题的关键在于理解DFS序与最短路径的关系,以及如何动态维护序列并快速更新答案。通过将问题转化为维护DFS序上相邻点的距离和,我们可以高效处理每次宝藏点的变动。

    • 1

    信息

    ID
    3445
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    10
    已通过
    1
    上传者