题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 个农场、\(M\) 条双向边的无向带权图,\(B\) 头奶牛分别住在不同农场。每头奶牛要从自己的农场 \(P_i\) 到 FJ 的农场 \(1\) 取礼物,再送到目标农场 \(Q_i\)。求每头奶牛的最短总路程。这是一个单源最短路 + 路径拆分问题,关键在于发现总路程 = \(dist(1, P_i) + dist(1, Q_i)\),即两次从农场 \(1\) 出发的最短路之和。

  2. 算法选择

    • 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]\)
  3. 关键步骤

    • 读取输入\(N, M, B\)\(M\) 条边 \((R_i, S_i, L_i)\)
    • 建图:无向图,链式前向星存储
    • Dijkstra:从节点 \(1\) 出发,计算到所有节点的最短距离 \(dist[]\)
    • 处理查询\(B\) 次):
      • 读入 \(P_i, Q_i\)
      • 输出 \(dist[P_i] + dist[Q_i]\)
    • 结束
  4. 时间/空间复杂度

    • 时间复杂度:\(O((N + M) \log N + B)\),Dijkstra 为 \(O((N+M)\log N)\),每次查询 \(O(1)\)
    • 空间复杂度:\(O(N + M)\),邻接表和距离数组
  5. 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
posted @ 2026-08-28 15:57  团爸讲算法  阅读(4)  评论(0)    收藏  举报