1 条题解
-
0
Hobson's Trains 题解
题目分析
这个问题要求计算对于每个火车站 A,能够在最多 k 趟旅程中到达 A 的站点个数(包括 A 本身)。铁路网络的特点是每个站点只有一条出边,这意味着整个网络由若干个基环树(每个连通分量是一个环,环上可能挂着树)组成。
关键思路
- 基环树结构:整个铁路网络由多个基环树组成,每个节点只有一条出边
- 反向图分析:原问题等价于在反向图中从每个节点出发,在 k 步内能到达的节点数
- 拓扑排序识别环:通过拓扑排序可以识别出环上的节点
- 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算法说明
- 暴力解法:对于每个节点,使用BFS在反向图中搜索k步内能到达的所有节点
- 优化思路:识别基环树结构,分别处理环上节点和树枝节点
- 拓扑排序:用于识别环结构,不在环上的节点会被拓扑排序处理掉
- DFS计算深度:从环上节点开始,计算每个节点到环的距离
复杂度分析
- 暴力解法:O(n × k),对于大数据会超时
- 优化版本:通过识别环结构可以减少不必要的计算
适用场景
- 小规模数据(n ≤ 1000):使用暴力解法
- 大规模数据(n ≤ 5×10^5):需要使用更复杂的基环树处理算法
对于大规模数据,完整的解决方案需要结合基环树分解、DFS序和高效的数据结构(如可持久化线段树)来实现O(n log n)的复杂度。
- 1
信息
- ID
- 5564
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者