题解:洛谷 P1673 Part Acquisition
【题目来源】
洛谷:P1673 [USACO05FEB] Part Acquisition S - 洛谷
【题目描述】
奶牛们接到了寻找一种新型挤奶机的任务,为此它们准备依次经过 \(N(1\le N\le 5\times 10^4)\) 颗行星,在行星上进行交易。为了方便,奶牛们已经给可能出现的 \(K(1\le K\le 10^3)\) 种货物进行了由 \(1\) 到 \(K\) 的标号。由于这些行星都不是十分发达。没有流通的货币,所以在每个市场里都只能用固定的一种货物去换取另一种货物。奶牛们带着一种上好的饲料从地球出发,希望在使用的物品的种类数量最少的情况下,最终得到所需要的机器。饲料的标号为 \(1\),所需要的机器的标号为 \(K\)。如果任务无法完成,输出 \(-1\)。
【输入】
第 \(1\) 行是两个数字 \(N\) 和 \(K\)。
第 \(2\) 到 \(N+1\) 行,每行是两个数字 \(A_i\) 和 \(B_i\),表示第 \(i\) 颗行星为得到 \(A_i\) 愿意提供 \(B_i\)。
【输出】
输出最少经手物品数。
【输入样例】
6 5
1 3
3 2
2 3
3 1
2 5
5 4
【输出样例】
4
【核心思想】
-
问题分析:给定 \(N\) 颗行星,每颗行星提供交易规则 \(A_i \to B_i\)(用 \(A_i\) 换 \(B_i\))。从货物 \(1\)(饲料)出发,求到达货物 \(K\)(机器)所需的最少经手物品数(即最短路径边数)。这是一个图论最短路/BFS问题,关键在于将交易规则建模为有向图的边,用 BFS 求从节点 \(1\) 到节点 \(K\) 的最短路径。
-
算法选择:
- 有向图建模:每个交易 \(A_i \to B_i\) 对应一条从 \(A_i\) 指向 \(B_i\) 的有向边
- BFS求最短路:所有边权为 \(1\)(每次交易经手一种物品),BFS 天然适合求无权图最短路
- 链式前向星:用邻接表存储图,\(O(1)\) 添加边,\(O(N)\) 遍历邻接点
-
关键步骤:
- 初始化:清空邻接表头数组 \(h\),\(idx = 0\)
- 建图:读入 \(N, K\) 和 \(N\) 条交易规则 \((A_i, B_i)\),调用
add(A_i, B_i)添加有向边 - BFS:
- 初始化距离数组 \(dist[i] = \infty\)(\(1 \le i \le K\)),\(dist[1] = 1\)(经手物品数包含起点)
- 起点 \(1\) 入队
- 取出队首 \(t\),遍历其所有邻接点 \(j\):
- 若 \(dist[j] > dist[t] + 1\):更新 \(dist[j] = dist[t] + 1\),\(j\) 入队
- 输出:若 \(dist[K] = \infty\) 输出 \(-1\),否则输出 \(dist[K]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N + K)\),每个节点和边最多被访问一次
- 空间复杂度:\(O(N + K)\),邻接表、距离数组和队列
-
BFS图最短路的核忈思想:
- 交易即边:将"用 \(A\) 换 \(B\)"抽象为有向边 \(A \to B\),问题转化为有向图中从 \(1\) 到 \(K\) 的最短路径
- 边权一致性:每次交易消耗 \(1\) 种物品,所有边权相等,BFS 的层序扩展保证首次到达即为最短
- 节点编号即货物编号:\(K\) 种货物对应 \(K\) 个节点,无需额外映射
- 不可达判断:BFS 结束后若 \(dist[K]\) 仍为 \(\infty\),说明不存在从 \(1\) 到 \(K\) 的交易链
- 适用于"规则转换 + 最少步骤"类问题,核心是将规则抽象为图边后用 BFS 求最短路
【解题思路】

【算法标签】
普及 #BFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义常量
const int N = 50005; // 最大节点数
const int M = N * 2; // 最大边数,无向图需要乘以2
int n; // 节点总数
int k; // 目标节点
int h[N]; // 邻接表头数组
int e[M]; // 边数组,存储边的终点
int ne[M]; // 邻接表next数组
int idx; // 边的索引计数器
int dist[N]; // 距离数组,存储从节点1到各节点的最短距离
// 添加边的函数
void add(int a, int b)
{
e[idx] = b; // 设置边的终点
ne[idx] = h[a]; // 新边指向原链表的头
h[a] = idx++; // 更新链表头,并递增索引
}
// 广度优先搜索函数,从节点1开始搜索
void bfs()
{
queue<int> q; // 创建队列用于BFS
q.push(1); // 从节点1开始搜索
// 初始化距离数组,将所有距离设为无穷大
for (int i = 1; i <= n; i++)
{
dist[i] = 1e9; // 1e9表示无穷大
}
dist[1] = 1; // 节点1到自身的距离为1(这里从1开始计数,不是0)
// BFS主循环
while (!q.empty())
{
int t = q.front(); // 取出队首节点
q.pop(); // 弹出队首节点
// 遍历节点t的所有邻居
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i]; // 邻居节点
// 如果找到更短的路径
if (dist[j] > dist[t] + 1)
{
dist[j] = dist[t] + 1; // 更新距离
q.push(j); // 将邻居节点加入队列
}
}
}
}
int main()
{
// 初始化邻接表头数组
memset(h, -1, sizeof(h));
// 读入节点数和目标节点
cin >> n >> k;
// 读入n-1条边(因为树有n-1条边)
for (int i = 1; i <= n; i++)
{
int u, v;
cin >> u >> v;
add(u, v); // 添加边u->v
// 注意:这里似乎缺少了add(v, u),应该是无向图的输入
}
// 执行BFS搜索
bfs();
// 输出结果
if (dist[k] == 1e9) // 如果目标节点不可达
{
cout << -1 << endl;
}
else // 如果可达
{
cout << dist[k] << endl;
}
return 0;
}
【运行结果】
6 5
1 3
3 2
2 3
3 1
2 5
5 4
4
浙公网安备 33010602011771号