题解:洛谷 P2984 Chocolate Giving
【题目来源】
洛谷:P2984 [USACO10FEB] Chocolate Giving S - 洛谷
【题目描述】
FJ 有 \(B\) 头奶牛 \((1\le B\le 25000)\),有 \(N(2\times B\le N\le 50000)\) 个农场,编号 \(1\) 到 \(N\),有 \(M(N-1\le M\le 100000)\) 条双向边,第 \(i\) 条边连接农场 \(R_i\) 和 \(S_i(1\le R_i\le N, 1\le S_i\le N)\),该边的长度是 \(L_i(1\le L_i\le 2000)\)。居住在农场 \(P_i\) 的奶牛 A \((1\le P_i\le N)\),想送一份新年礼物给居住在农场 \(Q_i(1\le Q_i\le N)\) 的奶牛 B,但是奶牛 A 必须先到 FJ(居住在编号 \(1\) 的农场)那里取礼物,然后再送给奶牛 B。你的任务是:奶牛 A 至少需要走多远的路程?
【输入】
- 第一行三个整数 \(N,M,B\)。
- 第 \(2\) 至 \(M+1\) 行,每行 \(3\) 个整数 \(R_i,S_i,L_i\)。
- 第 \(M+2\) 至 \(M+B+1\) 行,进行 \(B\) 次询问,每行 \(2\) 个整数 \(P_i ,Q_i\)。
【输出】
每次询问输出一个整数,即答案。
【输入样例】
6 7 3
1 2 3
5 4 3
3 1 1
6 1 9
3 4 2
1 4 4
3 2 2
2 4
5 1
3 6
【输出样例】
6
6
10
【核心思想】
-
问题分析:给定 \(N\) 个农场、\(M\) 条双向边的无向带权图,\(B\) 头奶牛分别住在不同农场。每头奶牛要从自己的农场 \(P_i\) 到 FJ 的农场 \(1\) 取礼物,再送到目标农场 \(Q_i\)。求每头奶牛的最短总路程。这是一个单源最短路 + 路径拆分问题,关键在于发现总路程 = \(dist(1, P_i) + dist(1, Q_i)\),即两次从农场 \(1\) 出发的最短路之和。
-
算法选择:
- Dijkstra(堆优化):从农场 \(1\) 运行一次 Dijkstra,得到到所有农场的最短距离
- 路径拆分:奶牛 A 的路径为 \(P_i \to 1 \to Q_i\),由于图是无向的,\(dist(P_i, 1) = dist(1, P_i)\),总路程 = \(dist[1][P_i] + dist[1][Q_i]\)
-
关键步骤:
- 读取输入:\(N, M, B\) 和 \(M\) 条边 \((R_i, S_i, L_i)\)
- 建图:无向图,链式前向星存储
- Dijkstra:从节点 \(1\) 出发,计算到所有节点的最短距离 \(dist[]\)
- 处理查询(\(B\) 次):
- 读入 \(P_i, Q_i\)
- 输出 \(dist[P_i] + dist[Q_i]\)
- 结束
-
时间/空间复杂度:
- 时间复杂度:\(O((N + M) \log N + B)\),Dijkstra 为 \(O((N+M)\log N)\),每次查询 \(O(1)\)
- 空间复杂度:\(O(N + M)\),邻接表和距离数组
-
Dijkstra 单源最短路的核忈思想:
- 无向图的对称性:\(dist(u, v) = dist(v, u)\),因此 \(P_i \to 1\) 的最短路就是 \(1 \to P_i\) 的最短路
- 一次计算多次查询:所有查询都涉及农场 \(1\),只需运行一次 Dijkstra,之后每个查询 \(O(1)\) 回答
- 堆优化效率:使用优先队列将 Dijkstra 复杂度从 \(O(N^2)\) 优化到 \(O((N+M)\log N)\),适合稀疏图
- 适用于"单源多查询 + 路径经过固定中转点"类问题,核心是利用无向对称性减少计算次数
【算法标签】
普及 #Dijkstra
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 50005, M = 100005 * 2;
typedef pair<int, int> PII;
int n, m, b; // n: 节点数, m: 边数, b: 查询次数
int h[N], e[M], w[M], ne[M], idx; // 邻接表
int dist[N]; // 距离数组
bool st[N]; // 标记数组
priority_queue<PII, vector<PII>, greater<PII>> pq; // 最小堆
// 添加无向边
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// Dijkstra算法求单源最短路径
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist)); // 初始化为无穷大
dist[1] = 0; // 起点距离为0
pq.push({0, 1}); // 起点加入堆
while (!pq.empty())
{
auto t = pq.top();
pq.pop();
int ver = t.second, distance = t.first; // 当前节点和距离
if (st[ver]) // 已处理过
{
continue;
}
st[ver] = true; // 标记已处理
for (int i = h[ver]; i != -1; i = ne[i]) // 遍历邻居
{
int j = e[i];
if (dist[j] > distance + w[i]) // 松弛操作
{
dist[j] = distance + w[i];
pq.push({dist[j], j});
}
}
}
}
int main()
{
cin >> n >> m >> b; // 输入节点数, 边数, 查询次数
memset(h, -1, sizeof(h));
for (int i = 1; i <= m; i++) // 建图
{
int u, v, w;
cin >> u >> v >> w;
add(u, v, w), add(v, u, w); // 无向图
}
dijkstra(); // 计算从节点1到所有节点的最短距离
while (b--) // 处理查询
{
int p, q;
cin >> p >> q; // 输入两个节点
cout << dist[p] + dist[q] << endl; // 输出距离和
}
return 0;
}
【运行结果】
6 7 3
1 2 3
5 4 3
3 1 1
6 1 9
3 4 2
1 4 4
3 2 2
2 4
6
5 1
6
3 6
10
浙公网安备 33010602011771号