2026年3月GESP真题及题解(C++七级):物流网络

题目描述
一个物流网络由 n n n 个城市和 m m m 条双向公路组成。每条公路都有两个属性:
- 运输费用 w i w_i wi
- 景观评分 b i b_i bi
当一辆运输车从城市 1 1 1 运送货物到城市 n n n 时,需要支付经过道路的运输费用之和。
为了推广旅游线路,物流公司推出了一项优惠政策:在运输路径上,可以免除景观评分最高 的那条公路的运输费用。如果有多条公路的景观评分同为最大值,则只免除其中 一条 的费用。
请你计算,从城市 1 1 1 到城市 n n n 的最小运输费用。
输入格式
第一行两个整数 n , m n,m n,m,分别表示城市数量和公路数量。
接下来 m m m 行,每行四个整数 u , v , w , b u,v,w,b u,v,w,b,表示存在一条连接城市 u u u 和城市 v v v 的双向公路,其中 w w w 为运输费用, b b b 为景观评分。
输出格式
输出一个整数,表示从城市 1 1 1 到城市 n n n 的最小费用。
如果无法到达,输出 -1。
输入输出样例 1
输入 1
3 3
1 2 10 5
2 3 20 6
1 3 100 1
输出 1
0
说明/提示
样例解释
路径 1 → 2 → 3 1\to 2\to 3 1→2→3:费用 10 + 20 10+20 10+20,最大美丽值 6 6 6(边 2 − 3 2-3 2−3)。免除 20 20 20,总花费 10 10 10。
路径 1 → 3 1\to 3 1→3:费用 100 100 100,最大美丽值 1 1 1(边 1 − 3 1-3 1−3)。免除 100 100 100,总花费 0 0 0。
数据范围
1 ≤ n ≤ 5000 1\leq n\leq 5000 1≤n≤5000, 1 ≤ m ≤ 5000 1\leq m\leq 5000 1≤m≤5000, 1 ≤ w , b ≤ 10 9 1\leq w,b\leq 10^9 1≤w,b≤109。
思路分析
题目要求从城市 1 到城市 n 找一条路径,可以免除路径上景观评分最高的一条边的费用,求最小总费用。
核心思路是枚举哪条边被免除 。对于每条边 e(u,v,w,b),如果它作为路径上评分最高的边,那么路径上所有边的评分都不超过 b,并且必须经过 e。
此时总费用 = (路径总费用) - w。在满足评分限制的子图中,经过 e 的最短路径费用可以表示为:
min( (1→u 的最短路不含 e) + w + (v→n 的最短路不含 e), (1→v 的最短路不含 e) + w + (u→n 的最短路不含 e) )
减去 w 后得到候选值 min( d1[u] + d2[v], d1[v] + d2[u] ),其中 d1、d2 是在评分 ≤ b 且不含 e 的子图上计算的最短路。
由于边数 ≤ 5000,我们可以将边按评分排序,然后逐批加入子图。对于每一批相同评分的边,先将它们加入子图,再对该批中的每条边分别运行两次 Dijkstra(排除自身)来计算候选值,并更新答案。总复杂度 O(m² log n),在本题数据范围内可行。
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5005; // 最大城市数
const int M = 5005; // 最大边数
const ll INF = 1e18; // 无穷大
struct Edge { // 邻接表中的边
int v; // 邻接点
ll w; // 费用
int id; // 边的原始编号
};
struct E { // 原始边信息
int u, v, w, b, id;
} e[M];
vector<Edge> adj[N]; // 邻接表(动态添加边)
int n, m;
bool cmp(E x, E y){
return x.b<y.b;
}
// Dijkstra 求从 s 出发,跳过编号为 skip 的边的最短距离数组 d[]
void dijkstra(int s, int skip, ll d[]) {
for (int i = 1; i <= n; ++i) d[i] = INF;
d[s] = 0;
priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq;
pq.push({0, s});
while (!pq.empty()) {
auto [dis, u] = pq.top(); pq.pop();
if (dis != d[u]) continue;
for (auto &ed : adj[u]) {
if (ed.id == skip) continue; // 跳过被免除的边
int v = ed.v;
ll nd = dis + ed.w;
if (nd < d[v]) {
d[v] = nd;
pq.push({nd, v});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int u, v, w, b;
cin >> u >> v >> w >> b;
e[i] = {u, v, w, b, i}; // 保存原始编号 i
}
// 按评分 b 升序排序
sort(e, e + m, cmp);
ll ans = INF;
// 逐批处理相同评分的边
for (int i = 0; i < m; ) {
int j = i;
while (j < m && e[j].b == e[i].b) ++j;
// 先将这一批所有边加入邻接表(子图扩大)
for (int k = i; k < j; ++k) {
int u = e[k].u, v = e[k].v, w = e[k].w, id = e[k].id;
adj[u].push_back({v, w, id});
adj[v].push_back({u, w, id});
}
// 对这批中的每条边,分别作为被免除边计算候选值
for (int k = i; k < j; ++k) {
int skip = e[k].id; // 当前被免除的边的编号
int u = e[k].u, v = e[k].v;
ll d1[N], d2[N];
dijkstra(1, skip, d1); // 从 1 出发,不含 skip
dijkstra(n, skip, d2); // 从 n 出发,不含 skip
// 两种方向经过 skip 边
if (d1[u] < INF && d2[v] < INF)
ans = min(ans, d1[u] + d2[v]);
if (d1[v] < INF && d2[u] < INF)
ans = min(ans, d1[v] + d2[u]);
}
i = j; // 处理下一批
}
if (ans == INF) cout << "-1\n";
else cout << ans << "\n";
return 0;
}
功能分析
-
数据结构
E存储原始边(起点、终点、费用、评分、编号),用于排序和后续查询。Edge存储在邻接表中(邻接点、费用、编号),便于 Dijkstra 中快速跳过指定边。adj[]为动态邻接表,随着评分升高逐步添加边,保证子图单调扩大。
-
核心算法
- 排序:按评分升序排列,确保处理到某条边时,所有评分更小的边都已加入子图。
- 分批处理:相同评分的边同时加入子图,然后对该批中的每条边分别计算候选值。
- Dijkstra:对每条边执行两次(起点 1 和起点 n),并跳过当前被免除的边,得到两个距离数组。
- 候选值计算 :两种方向
d1[u]+d2[v]和d1[v]+d2[u]取最小值,更新全局答案。
-
复杂度
- 最多进行
2m次 Dijkstra(每条边两次),每次 Dijkstra 遍历当前子图的所有边(平均约m/2条),堆操作O(当前边数 log n)。 - 总时间复杂度
O(m² log n)≈5000² × 12 ≈ 3×10⁸。 - 空间复杂度
O(n+m),邻接表存储所有边。
- 最多进行
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
【秘籍汇总】(完整csp信奥赛C++学习资料):
1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转

2、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html 点击跳转
4、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):
https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13125089.html 点击跳转
5、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}
