GESP2026年3月认证C++七级( 第三部分编程题(1、物流网络))精讲


一、题目

有:

  • n个城市

  • m条双向道路


每条道路:

复制代码
u v w b

含义:

复制代码
u ----- v

w:运输费用

b:景观评分

运输:

从城市1到城市n。

优惠:

路径中景观评分最高的一条道路免费。


求:

最小运输费用。


二、先不要写代码,理解本质

我们先看一个例子。

道路:

复制代码
      10,b=5
1 ------------ 2
               |
               |
             20,b=8
               |
               |
               3

正常:

复制代码
10+20=30

但是:

景观最高:

复制代码
b=8

所以:

20免费。

答案:

复制代码
10

学生第一反应:

枚举所有路径。

例如:

DFS:

复制代码
1→2→3
1→4→5→3
...

问题:

路径数量爆炸。

所以:

不能枚举路径。


三、关键核心观察

问同学们:

如果我告诉你:

哪条边免费

问题是不是简单了?


例如:

假设:

复制代码
2---3

免费。


那么:

答案:

复制代码
1到2最短距离

+

3到n最短距离

也就是:

复制代码
dist(1,2)+dist(3,n)

所以:

真正的问题:

找到那条应该免费的边。


四、为什么可以按 b 排序?

这是本题的重点。

我们按照:

景观评分:

小 → 大

例如:

复制代码
道路:

A b=2
B b=5
C b=9

处理顺序:

复制代码
A
B
C

当处理 C:

因为:

C的景观最大。

所以:

如果最终路径经过C:

C一定可以免费。

于是:

答案:

复制代码
1到C左边
+
C右边到n

五、理解程序的大框架

首先要建立图。

但是注意:

这里不是一开始所有道路都加入。

而是:

一条一条加入。


为什么?

因为:

当前正在处理的道路:

就是:

"可能免费的最大景观道路"。


六、建立两个距离数组

代码:

复制代码
vector<long long> dist1(n+1,1e18);
vector<long long> distn(n+1,1e18);

两个数组。


dist1

含义:

当前已经加入的道路中,从城市1到每个城市的最短距离。

例如:

复制代码
dist1[5]

表示:

复制代码
1 → 5

最低费用。

初始化:

复制代码
dist1[1]=0;

因为:

自己到自己:

0。


distn

含义:

当前道路中,从城市n到每个城市的最短距离。

初始化:

复制代码
distn[n]=0;

为什么需要两个?

因为免费道路:

复制代码
u -------- v

可能:

左边来自1:

复制代码
1 → u

右边到n:

复制代码
v → n

也可能反过来。


七、最关键的一行代码

复制代码
ans=min(
ans,
min(
dist1[road.u]+distn[road.v],
dist1[road.v]+distn[road.u]
));

这是整个题目的灵魂。

假设当前道路:

复制代码
u -------- v

费用w

景观b

现在假设:

它就是最高景观道路。

因为免费:

所以:

不用加w。

情况1:

复制代码
1 → u

免费

v → n

费用:

复制代码
dist1[u]+distn[v]

情况2:

复制代码
1 → v

免费

u → n

费用:

复制代码
dist1[v]+distn[u]

取最小。


八、加入当前道路

代码:

复制代码
graph[road.u].push_back(&road);

graph[road.v].push_back(&road);

什么意思?

之前:

图里面:

复制代码
景观更小的道路

现在:

加入:

复制代码
当前道路

以后:

它可以参与后面的最短路。


九、relax函数

代码:

复制代码
const auto relax =
[&](vector<long long> &dist, queue<int> &q)

它做什么?

一句话:

新加入道路后,重新传播可能变短的距离。


看:

复制代码
if(dist[u]+road->w < dist[v])

经典松弛。

如果:

经过u到v更短:

更新。

例如:

原来:

复制代码
dist[v]=100

发现:

复制代码
dist[u]=20

边=30

那么:

复制代码
20+30=50

更优。

更新:

复制代码
dist[v]=50

十、完整程序:

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;


const long long INF = 1e18;


// 道路
struct Road
{
    int u;  // 起点
    int v;  // 终点
    int w;  // 费用
    int b;  // 景观


    // 排序规则
    bool operator < (const Road &other) const
    {
        if(b != other.b)
            return b < other.b;

        return w < other.w;
    }
};


// 图中的边
struct Edge
{
    int to;
    int cost;
};



// 更新最短距离
void relax(
    vector<vector<Edge>>& graph,
    vector<long long>& dist,
    queue<int>& q
)
{

    while(!q.empty())
    {
        int u = q.front();
        q.pop();


        for(auto edge : graph[u])
        {
            int v = edge.to;
            int w = edge.cost;


            // 松弛操作
            if(dist[u] + w < dist[v])
            {
                dist[v] = dist[u] + w;

                q.push(v);
            }
        }
    }

}



int main()
{

    int n,m;

    cin >> n >> m;



    vector<Road> roads(m);



    // 输入道路
    for(int i=0;i<m;i++)
    {
        cin >> roads[i].u
            >> roads[i].v
            >> roads[i].w
            >> roads[i].b;
    }



    // 按景观排序
    sort(
        roads.begin(),
        roads.end()
    );



    // 邻接表
    vector<vector<Edge>> graph(n+1);



    // 从1号点出发的距离
    vector<long long> distFromStart(
        n+1,
        INF
    );


    // 从n号点出发的距离
    vector<long long> distFromEnd(
        n+1,
        INF
    );



    distFromStart[1]=0;

    distFromEnd[n]=0;



    queue<int> startQueue;

    queue<int> endQueue;



    long long answer = INF;



    // 逐条加入道路
    for(int i=0;i<m;i++)
    {

        Road road = roads[i];


        /*
            假设当前道路免费

            情况1:
            1 -> u
            免费
            v -> n


            情况2:
            1 -> v
            免费
            u -> n
        */


        answer = min(
            answer,
            min(
                distFromStart[road.u]
                +
                distFromEnd[road.v],


                distFromStart[road.v]
                +
                distFromEnd[road.u]
            )
        );



        // 加入道路
        graph[road.u].push_back(
            {
                road.v,
                road.w
            }
        );


        graph[road.v].push_back(
            {
                road.u,
                road.w
            }
        );



        // 更新起点距离

        startQueue.push(road.u);
        startQueue.push(road.v);


        relax(
            graph,
            distFromStart,
            startQueue
        );



        // 更新终点距离

        endQueue.push(road.u);
        endQueue.push(road.v);


        relax(
            graph,
            distFromEnd,
            endQueue
        );

    }



    if(answer == INF)

        cout << -1 << endl;

    else

        cout << answer << endl;



    return 0;
}

十一、程序详细解析:

整个程序可以分成:

  1. 道路结构设计

  2. 排序

  3. 建图

  4. 维护两个最短路数组

  5. 枚举免费道路更新答案

  6. 动态加入道路并更新距离

下面逐步拆解。


1、整体流程图

先看程序的大框架:

复制代码
输入道路

      |
      v

按照景观 b 排序

      |
      v

建立空图


distFromStart:
1号点到所有点距离


distFromEnd:
n号点到所有点距离


      |
      v


逐条处理道路


    ① 假设当前道路免费
       更新答案


    ② 加入道路


    ③ 更新两个最短距离


      |
      v


输出答案

2、头文件

复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

分别对应:

头文件 作用
iostream 输入输出
vector 动态数组
queue 队列
algorithm sort、min

3、INF定义

复制代码
const long long INF = 1e18;

表示:

无穷大。

为什么不用:

复制代码
int

因为:

道路费用可能累加。

例如:

100000条道路:

复制代码
100000 × 100000
=
1e10

超过 int:

复制代码
2.1e9

所以:

使用:

复制代码
long long

3、道路结构体 Road

代码:

复制代码
struct Road
{
    int u;
    int v;
    int w;
    int b;
};

一条道路:

复制代码
u -------- v

包含:

变量 含义
u 城市u
v 城市v
w 费用
b 景观值

例如:

输入:

复制代码
1 3 20 8

表示:

复制代码
1 -------- 3

费用20

景观8

4、为什么 Road 要重载 <

代码:

复制代码
bool operator < (const Road &other) const
{
    if(b != other.b)
        return b < other.b;

    return w < other.w;
}

这是为了:

复制代码
sort(roads.begin(),roads.end());

排序。


5、规则:

第一关键字:

景观 b

小的在前:

例如:

复制代码
b=3
b=5
b=8

第二关键字:

如果景观一样:

费用小的先。

例如:

复制代码
(费用10, 景观5)

(费用20, 景观5)

排序:

复制代码
10,5

20,5

6、Edge结构体

复制代码
struct Edge
{
    int to;
    int cost;
};

这个和 Road 不一样。

Road:

保存原始道路信息。

Edge:

保存图里的邻接信息。

例如道路:

复制代码
1 ---- 2

费用10

加入图:

复制代码
graph[1]
{
    to=2,
    cost=10
}


graph[2]
{
    to=1,
    cost=10
}

7、relax函数

函数:

cpp 复制代码
void relax(
    vector<vector<Edge>>& graph,
    vector<long long>& dist,
    queue<int>& q
){
 .......
}

作用:

新加入道路后,重新传播最短距离。


参数1:

复制代码
graph

当前图。


参数2:

复制代码
dist

距离数组。

可能:

复制代码
distFromStart

也可能:

复制代码
distFromEnd

参数3:

复制代码
queue<int>& q

需要重新检查的城市。


8、relax内部

(1)取队首城市

复制代码
int u=q.front();

q.pop();

例如:

队列:

复制代码
2 5

取:

复制代码
2

(2) 遍历邻边

复制代码
for(auto edge : graph[u])

例如:

当前城市:

复制代码
2

邻接:

复制代码
2--1

2--3

2--5

全部检查。


(3)松弛

核心:

复制代码
if(dist[u]+w < dist[v])

什么意思?

假设:

现在:

复制代码
到u距离 = 20

道路:

复制代码
u---v

费用30

那么:

经过u到v:

复制代码
20+30=50

如果:

原来:

复制代码
dist[v]=100

那么:

更新。


9、读取输入

复制代码
int n,m;

cin>>n>>m;

城市数:

n

道路数:

m


10、保存道路

复制代码
vector<Road> roads(m);

开一个数组:

保存所有道路。


输入:

复制代码
for(int i=0;i<m;i++)
{
    cin>>
    roads[i].u
    ...
}

例如:

输入:

复制代码
1 2 10 5

保存:

复制代码
roads[0]

u=1
v=2
w=10
b=5

11、排序

复制代码
sort(
roads.begin(),
roads.end()
);

目的:

让道路:

按照景观:

小 → 大

例如:

原来:

复制代码
8
2
5

变:

复制代码
2
5
8

12、建立图

复制代码
vector<vector<Edge>> graph(n+1);

邻接表。

为什么:

n+1?

因为:

城市编号:

复制代码
1~n

0不用。


13、两个距离数组

distFromStart

复制代码
vector<long long> distFromStart(n+1,INF);

表示:

复制代码
1号城市到其它城市

初始化:

复制代码
distFromStart[1]=0;

因为:

自己到自己:

0。


distFromEnd

复制代码
vector<long long> distFromEnd(n+1,INF);

表示:

复制代码
n号城市到其它城市

初始化:

复制代码
distFromEnd[n]=0;

14、为什么需要两个距离?(重点)

当前免费道路:

复制代码
u -------- v

如果免费:

路径可能:

方向1

复制代码
1 → u

免费

v → n

费用:

复制代码
distFromStart[u]
+
distFromEnd[v]

方向2

复制代码
1 → v

免费

u → n

费用:

复制代码
distFromStart[v]
+
distFromEnd[u]

所以必须两个方向。


15、主循环

复制代码
for(int i=0;i<m;i++)

逐条处理排序后的道路。


16、最核心代码

复制代码
answer = min(
answer,
min(
distFromStart[road.u]
+
distFromEnd[road.v],


distFromStart[road.v]
+
distFromEnd[road.u]
)
);

这是整题灵魂。


假设:

当前道路:

复制代码
u -------- v

并且:

它是最高景观道路。

那么:

它免费。

所以:

不用加:

复制代码
road.w

两个方案:

方案1:

复制代码
1 → u
免费
v → n

方案2:

复制代码
1 → v
免费
u → n

取小。


17、加入道路

复制代码
graph[road.u].push_back(
{
road.v,
road.w
}
);

加入:

u到v。


另一方向:

复制代码
graph[road.v].push_back(
{
road.u,
road.w
}
);

因为:

道路双向。


18、更新起点距离

复制代码
startQueue.push(road.u);
startQueue.push(road.v);

新加入道路:

影响:

u、v附近。

加入队列。

然后:

复制代码
relax(
graph,
distFromStart,
startQueue
);

更新:

1号点距离。


19、更新终点距离

同理:

复制代码
relax(
graph,
distFromEnd,
endQueue
);

不过:

这里维护的是:

n到其它点。


20、输出

复制代码
if(answer==INF)

cout<<-1;

说明:

无法到达。

否则:

输出最小费用。


十二、总结算法思想

普通最短路:

复制代码
固定道路
求最短

本题:

道路有特殊优惠:

复制代码
最高景观道路免费

怎么办?


转换:

复制代码
枚举免费道路

↓

左边最短路

+

右边最短路

↓

更新答案

十三、一句话记忆

排序让当前道路成为"可能免费的最高景观道路",维护两个方向的最短距离,然后尝试免费。


十二、最后总结

这道题可以浓缩成一句话:

按景观值排序,每次把当前道路看成"免费道路",利用已经加入的小景观道路维护从起点和终点的最短距离,更新答案。


相关推荐
鱼子星_1 小时前
【C++】深入剖析list:list及其双向迭代器实现
开发语言·数据结构·c++·笔记·stl·list
tudousisi2222 小时前
P4667 [BalticOI 2011] Switch the Lamp On复盘
c++
江屿风2 小时前
【C++笔记】【二叉搜索树】流食般投喂
开发语言·数据结构·c++·笔记
海绵天哥3 小时前
LeetCode Hot 100 | 链表(下)· 分组翻转与设计(C++ 题解)
c++·leetcode·链表
choumin3 小时前
行为型模式——中介者模式
c++·设计模式·中介者模式·行为型模式
Lee_jerome14 小时前
《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》
c语言·开发语言·arm开发·c++·编译·cmake·cmakelists
冰心孤城14 小时前
C++ 与 C#混合编程 示例 (基于VS)
java·c++·c#
一只旭宝18 小时前
C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析
开发语言·c++·面试