1 条题解

  • 0
    @ 2025-11-25 9:12:16

    题目分析

    问题概述

    我们有一个包含 N+1N+1 个行星的树结构,地球编号为 NN。需要从地球出发访问 KK 个起源行星,访问路径上的所有行星构成集合 SS。题目要求处理两种查询:

    1. 查询集合 SS 的大小
    2. 对于给定的 xS,d,rx \in S, d, r,求距离 xxdd 的所有行星中按人口数排序的第 rr 小的行星

    关键观察

    1. 树结构性质NN 条边连接 N+1N+1 个节点,构成一棵树
    2. 集合 SS 的构成SS 是从地球到所有起源行星的简单路径的并集
    3. 距离计算:在树中,两点距离 = 深度[u] + 深度[v] - 2 × 深度[LCA(u,v)]

    解法思路

    方法:LCA预处理 + 路径标记 + 距离分类

    步骤1:预处理LCA信息

    使用DFS和二进制提升法预处理每个节点的父节点信息,用于快速计算LCA和距离。

    步骤2:确定集合S

    从地球出发,标记到每个起源行星路径上的所有节点:

    • 使用LCA找到路径
    • 标记路径上的所有节点

    步骤3:处理查询

    • 类型1:直接输出集合S的大小
    • 类型2:对于每个查询,需要高效找到距离x为d的所有行星并按人口排序

    高效实现

    核心算法

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 100005;
    const int LOG = 17;
    
    int n, k, q;
    vector<int> adj[MAXN];
    int population[MAXN];
    vector<int> origins;
    bool inS[MAXN];
    int szS = 0;
    
    // LCA相关
    int depth[MAXN];
    int parent[MAXN][LOG];
    
    void dfs(int u, int p, int d) {
        depth[u] = d;
        parent[u][0] = p;
        for (int i = 1; i < LOG; i++) {
            if (parent[u][i-1] != -1) {
                parent[u][i] = parent[parent[u][i-1]][i-1];
            }
        }
        
        for (int v : adj[u]) {
            if (v != p) {
                dfs(v, u, d + 1);
            }
        }
    }
    
    int lca(int u, int v) {
        if (depth[u] < depth[v]) swap(u, v);
        
        // u提升到与v同一深度
        for (int i = LOG - 1; i >= 0; i--) {
            if (depth[u] - (1 << i) >= depth[v]) {
                u = parent[u][i];
            }
        }
        
        if (u == v) return u;
        
        // 同时提升
        for (int i = LOG - 1; i >= 0; i--) {
            if (parent[u][i] != parent[v][i]) {
                u = parent[u][i];
                v = parent[v][i];
            }
        }
        return parent[u][0];
    }
    
    int distance(int u, int v) {
        return depth[u] + depth[v] - 2 * depth[lca(u, v)];
    }
    
    void markPath(int u, int v) {
        int l = lca(u, v);
        
        // 标记u到lca的路径
        int cur = u;
        while (cur != l) {
            if (!inS[cur]) {
                inS[cur] = true;
                szS++;
            }
            cur = parent[cur][0];
        }
        
        // 标记v到lca的路径
        cur = v;
        while (cur != l) {
            if (!inS[cur]) {
                inS[cur] = true;
                szS++;
            }
            cur = parent[cur][0];
        }
        
        // 标记lca
        if (!inS[l]) {
            inS[l] = true;
            szS++;
        }
    }
    

    查询处理优化

    对于类型2查询,直接计算会超时,需要预处理:

    // 为每个可能的距离预先计算行星列表
    vector<vector<int>> planetsByDist[MAXN];
    
    void precomputeDistances() {
        for (int i = 0; i <= n; i++) {
            planetsByDist[i].resize(MAXN);
        }
        
        // 对每对节点计算距离并分类
        for (int i = 0; i <= n; i++) {
            for (int j = i; j <= n; j++) {
                int dist = distance(i, j);
                if (dist < MAXN) {
                    planetsByDist[i][dist].push_back(j);
                    if (i != j) {
                        planetsByDist[j][dist].push_back(i);
                    }
                }
            }
        }
        
        // 对每个距离列表按人口排序
        for (int i = 0; i <= n; i++) {
            for (int dist = 0; dist < MAXN; dist++) {
                if (!planetsByDist[i][dist].empty()) {
                    sort(planetsByDist[i][dist].begin(), 
                         planetsByDist[i][dist].end(),
                         [&](int a, int b) {
                             return population[a] < population[b];
                         });
                }
            }
        }
    }
    

    主程序

    int main() {
        ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        
        cin >> n >> k >> q;
        
        // 读入数据
        for (int i = 0; i <= n; i++) {
            cin >> population[i];
        }
        
        origins.resize(k);
        for (int i = 0; i < k; i++) {
            cin >> origins[i];
        }
        
        for (int i = 0; i < n; i++) {
            int u, v;
            cin >> u >> v;
            adj[u].push_back(v);
            adj[v].push_back(u);
        }
        
        // 初始化
        memset(parent, -1, sizeof(parent));
        
        // 预处理LCA
        dfs(n, -1, 0);
        
        // 标记集合S
        inS[n] = true;
        szS = 1;
        for (int origin : origins) {
            markPath(n, origin);
        }
        
        // 预处理距离信息
        precomputeDistances();
        
        // 处理查询
        while (q--) {
            int type;
            cin >> type;
            
            if (type == 1) {
                cout << szS << "\n";
            } else {
                int x, d, r;
                cin >> x >> d >> r;
                cout << planetsByDist[x][d][r - 1] << "\n";
            }
        }
        
        return 0;
    }
    

    空间优化版本

    对于大数据,上述方法空间开销太大。可以采用按需计算的策略:

    // 按需计算距离查询
    int queryType2(int x, int d, int r) {
        vector<int> candidates;
        
        // 使用BFS收集距离x为d的所有节点
        queue<pair<int, int>> q;
        vector<bool> visited(n + 1, false);
        q.push({x, 0});
        visited[x] = true;
        
        while (!q.empty()) {
            auto [node, dist] = q.front();
            q.pop();
            
            if (dist == d) {
                candidates.push_back(node);
                continue;
            }
            
            if (dist > d) continue;
            
            for (int neighbor : adj[node]) {
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    q.push({neighbor, dist + 1});
                }
            }
        }
        
        // 按人口排序
        sort(candidates.begin(), candidates.end(),
            [&](int a, int b) {
                return population[a] < population[b];
            });
        
        return candidates[r - 1];
    }
    

    复杂度分析

    方法1:完全预处理

    • 时间复杂度
      • 预处理:O(n2logn)O(n^2 \log n)(距离计算)
      • 查询:O(1)O(1)
    • 空间复杂度O(n2)O(n^2)
    • 适用性:适合 n2000n \leq 2000 的情况

    方法2:按需计算

    • 时间复杂度
      • 预处理:O(nlogn)O(n \log n)(仅LCA)
      • 查询:O(n+dmax×度数和+nlogn)O(n + d_{max} \times \text{度数和} + n \log n)
    • 空间复杂度O(n)O(n)
    • 适用性:适合大数据,但查询较慢

    方法3:平衡方案

    对于 n105n \leq 10^5,可以:

    1. 预处理所有节点到地球的距离
    2. 对于查询,利用树的性质和LCA快速计算距离
    3. 使用哈希表缓存常见查询

    算法总结

    1. LCA预处理:使用二进制提升法,支持 O(logn)O(\log n) 的LCA查询
    2. 路径标记:通过LCA找到路径并标记访问节点
    3. 距离查询优化
      • 小数据:完全预处理
      • 大数据:按需BFS + 排序
    4. 排序策略:按人口数排序,使用自定义比较函数

    关键技巧

    1. 二进制提升:高效计算LCA
    2. 路径分解:将路径分解为u→LCA和v→LCA两部分
    3. 距离性质:在树中,距离满足三角不等式
    4. 查询优化:根据数据规模选择合适的预处理策略

    这道题综合考察了树的基本操作、LCA算法、路径处理和查询优化,是一道很好的综合性树问题。

    • 1

    信息

    ID
    5436
    时间
    5000ms
    内存
    1024MiB
    难度
    9
    标签
    递交数
    3
    已通过
    1
    上传者