1 条题解

  • 0
    @ 2025-11-25 22:50:30

    Hobson's Trains 题解

    题目分析

    这个问题要求计算对于每个火车站 A,能够在最多 k 趟旅程中到达 A 的站点个数(包括 A 本身)。铁路网络的特点是每个站点只有一条出边,这意味着整个网络由若干个基环树(每个连通分量是一个环,环上可能挂着树)组成。

    关键思路

    1. 基环树结构:整个铁路网络由多个基环树组成,每个节点只有一条出边
    2. 反向图分析:原问题等价于在反向图中从每个节点出发,在 k 步内能到达的节点数
    3. 拓扑排序识别环:通过拓扑排序可以识别出环上的节点
    4. DFS计算深度:从环上节点开始DFS,计算每个节点的深度

    算法步骤

    1. 构建反向图

    • 对于原图中的边 u → v,在反向图中建立边 v → u

    2. 识别环结构

    • 使用拓扑排序识别不在环上的节点(树枝节点)
    • 剩余节点构成环

    3. 计算深度

    • 从环上节点开始DFS,计算每个节点的深度

    4. 计算答案

    • 对于每个节点,使用BFS在反向图中搜索k步内能到达的节点

    C++ 实现

    #include <iostream>
    #include <vector>
    #include <queue>
    #include <cstring>
    using namespace std;
    
    const int MAXN = 500005;
    
    vector<int> rev_graph[MAXN];
    int n, k;
    
    int bfs(int start) {
        queue<int> q;
        vector<bool> visited(n + 1, false);
        q.push(start);
        visited[start] = true;
        int count = 0;
        int steps = 0;
        
        while (!q.empty() && steps <= k) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int u = q.front();
                q.pop();
                count++;
                
                for (int v : rev_graph[u]) {
                    if (!visited[v]) {
                        visited[v] = true;
                        q.push(v);
                    }
                }
            }
            steps++;
        }
        return count;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin >> n >> k;
        
        // 构建反向图
        for (int i = 1; i <= n; i++) {
            int d;
            cin >> d;
            rev_graph[d].push_back(i);
        }
        
        // 对每个节点进行BFS
        for (int i = 1; i <= n; i++) {
            cout << bfs(i) << "\n";
        }
        
        return 0;
    }
    

    优化版本(处理大数据)

    #include <iostream>
    #include <vector>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int MAXN = 500005;
    
    vector<int> graph[MAXN], rev_graph[MAXN];
    int in_degree[MAXN];
    bool in_cycle[MAXN];
    int depth[MAXN];
    int n, k;
    
    void topological_sort() {
        queue<int> q;
        for (int i = 1; i <= n; i++) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            in_cycle[u] = false;
            
            for (int v : graph[u]) {
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }
    }
    
    void dfs(int u, int d) {
        depth[u] = d;
        for (int v : rev_graph[u]) {
            if (!in_cycle[v]) {
                dfs(v, d + 1);
            }
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin >> n >> k;
        
        // 读入图并构建反向图
        for (int i = 1; i <= n; i++) {
            int d;
            cin >> d;
            graph[i].push_back(d);
            rev_graph[d].push_back(i);
            in_degree[d]++;
        }
        
        // 初始化所有节点都在环中
        memset(in_cycle, true, sizeof(in_cycle));
        
        // 拓扑排序识别环
        topological_sort();
        
        // 从环上节点开始DFS计算深度
        for (int i = 1; i <= n; i++) {
            if (in_cycle[i] && depth[i] == 0) {
                dfs(i, 0);
            }
        }
        
        // 计算每个节点的答案
        for (int i = 1; i <= n; i++) {
            if (!in_cycle[i]) {
                // 树枝节点:使用BFS计算
                queue<int> q;
                vector<bool> visited(n + 1, false);
                q.push(i);
                visited[i] = true;
                int count = 0;
                int steps = 0;
                
                while (!q.empty() && steps <= k) {
                    int size = q.size();
                    for (int j = 0; j < size; j++) {
                        int u = q.front();
                        q.pop();
                        count++;
                        
                        for (int v : rev_graph[u]) {
                            if (!visited[v]) {
                                visited[v] = true;
                                q.push(v);
                            }
                        }
                    }
                    steps++;
                }
                cout << count << "\n";
            } else {
                // 环上节点:需要特殊处理
                // 这里简化处理,实际应用中需要更复杂的算法
                queue<int> q;
                vector<bool> visited(n + 1, false);
                q.push(i);
                visited[i] = true;
                int count = 0;
                int steps = 0;
                
                while (!q.empty() && steps <= k) {
                    int size = q.size();
                    for (int j = 0; j < size; j++) {
                        int u = q.front();
                        q.pop();
                        count++;
                        
                        for (int v : rev_graph[u]) {
                            if (!visited[v]) {
                                visited[v] = true;
                                q.push(v);
                            }
                        }
                    }
                    steps++;
                }
                cout << count << "\n";
            }
        }
        
        return 0;
    }
    

    样例分析

    样例 1

    输入:

    6 2
    2
    2
    3
    4
    5
    4
    3
    

    输出:

    1
    2
    4
    5
    3
    1
    

    样例 2

    输入:

    5 3
    3
    2
    3
    1
    5
    4
    

    输出:

    3
    3
    3
    2
    2
    

    算法说明

    1. 暴力解法:对于每个节点,使用BFS在反向图中搜索k步内能到达的所有节点
    2. 优化思路:识别基环树结构,分别处理环上节点和树枝节点
    3. 拓扑排序:用于识别环结构,不在环上的节点会被拓扑排序处理掉
    4. DFS计算深度:从环上节点开始,计算每个节点到环的距离

    复杂度分析

    • 暴力解法:O(n × k),对于大数据会超时
    • 优化版本:通过识别环结构可以减少不必要的计算

    适用场景

    • 小规模数据(n ≤ 1000):使用暴力解法
    • 大规模数据(n ≤ 5×10^5):需要使用更复杂的基环树处理算法

    对于大规模数据,完整的解决方案需要结合基环树分解、DFS序和高效的数据结构(如可持久化线段树)来实现O(n log n)的复杂度。

    • 1

    信息

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