题解:洛谷 P3115 Cow Routing
【题目来源】
洛谷:P3115 [USACO15JAN] Cow Routing S
【题目描述】
Bessie 对她农场那寒冷的天气感到厌烦,于是她准备坐飞机到一个更温暖的地方度假。不幸的是,她发现只有一个航空公司:Air Bovinia,愿意卖票给牛,并且这些票的结构有些复杂。
Air Bovinia 拥有 \(n\) 架飞机,每架飞机都有一个经过两个及以上的城市的特殊航线。举个例子:一架飞机可以从城市 \(1\) 出发,然后飞往城市 \(5\),再飞到城市 \(2\),最后飞到城市 \(8\)。注意航线是单向的。任何城市都不会在同一条航线上出现多次。如果 Bessie 选择了一条航线,那么她可以从航线上的任意一个城市上飞机,然后在途中任意一个城市下飞机。她不必从航线的起点上飞机,再从航线的终点下飞机。每条航线都有一个确定的花费,只要她搭乘了这个航班,她就必须支付这个航班的全额花费,不论她途经了几个城市。如果 Bessie 多次搭乘了某个航班,那么每次搭乘 Bessie 都必须支付航班的花费。
Bessie 想要找到从她农场所在的城市(城市 \(A\))到她目的地所在城市(城市 \(B\))最便宜的路线。请你告诉她他最少要花多少钱,并告诉她在此基础上她最少要经过几段航线,也即经过的城市数量 \(-1\)(包括起点和终点)。
【输入】
第一行三个整数 \(A,B,n\)。
接下来 \(2n\) 行,每两行描述一次航班。第一行包括航线的花费 \(cost\) 与经过的城市数 \(len\)。第二行 \(len\) 个整数,表示航班依次经过的城市。
【输出】
一行两个整数,输出 Bessie 旅行最少要花的钱以及在此基础上最少的经过航线数量。
如果 Bessie 不能从她的农场到达她的目的地,则输出 -1 -1。
【输入样例】
3 4 3
3 5
1 2 3 4 5
2 3
3 5 4
1 2
1 5
【输出样例】
2 2
【核心思想】
-
问题分析:给定 \(N\) 条航线,每条航线包含一系列城市序列,花费为 \(cost\)。Bessie 可以从航线上的任意城市上飞机,在之后的任意城市下飞机,每次搭乘支付全额 \(cost\)。求从城市 \(A\) 到城市 \(B\) 的最小花费,在花费最少的前提下经过的城市数最少。这是一个双目标最短路问题,关键在于将航线转化为城市之间的直达边,然后用 SPFA 按花费优先、其次城市数最少的策略求最短路。
-
算法选择:
- 航线拆边:每条航线上任意两个城市 \(x < y\) 之间建立有向边,花费为 \(cost\),经过城市数为 \(y - x\)
- SPFA 双目标最短路:用
pair<int, int>存储距离(花费,城市数),松弛时先比较花费,花费相同则比较城市数
-
关键步骤:
- 初始化:
- 读取 \(s\)(起点)、\(t\)(终点)、\(n\)(航线数)
- 初始化航班矩阵 \(g[u][v]\) 为 \(\infty\)
- 航线拆边:
- 对每条航线,读取 \(cost\)(花费)和 \(k\)(城市数),以及城市序列 \(a[1..k]\)
- 枚举 \(x\) 从 \(1\) 到 \(k\),\(y\) 从 \(x+1\) 到 \(k\):
- \(g[a[x]][a[y]] = \min(g[a[x]][a[y]],\ \{cost,\ y-x\})\)
- 保留花费最小、花费相同则城市数最少的边
- SPFA 求最短路:
- 初始化 \(\text{dis}[s] = \{0, 0\}\),其余为 \(\infty\)
- 队列中加入起点 \(s\)
- 取出队首 \(u\),遍历所有可达城市 \(v\):
- 新距离 \(\text{new\_dis} = \{\text{dis}[u].\text{first} + g[u][v].\text{first},\ \text{dis}[u].\text{second} + g[u][v].\text{second}\}\)
- 若 \(\text{dis}[v] > \text{new\_dis}\)(花费更小,或花费相同城市数更少),更新并入队
- 输出:若 \(\text{dis}[t]\) 为 \(\infty\),输出
-1 -1;否则输出花费和城市数
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot K^2 + V \cdot E)\),\(K\) 为每条航线最大城市数(\(\le 100\)),\(V \le 1000\),\(E \le N \cdot K^2\)
- 空间复杂度:\(O(V^2)\),航班矩阵
-
双目标 SPFA 的核心思想:
- 航线拆边策略:将"从航线任意点上、任意点下"转化为城市间的直达边,避免复杂的状态设计
- pair 比较规则:
pair的默认比较先比first(花费)再比second(城市数),天然支持双目标优先级 - SPFA 松弛:与普通 SPFA 相同,只是距离类型变为 pair,比较规则自动处理双目标
- 不可达判断:用极大值判断是否到达终点
- 适用于双目标最短路、航线换乘、花费与路径长度权衡类问题
【算法标签】
普及+ #SPFA
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用long long防止花费累加溢出
typedef pair<int, int> PII; // 定义对类型:first为花费,second为经过城市数
const int N = 1005; // 最大城市编号
int s, t, n; // s:起点城市, t:终点城市, n:航线数量
int a[N]; // 临时存储每条航线上的城市序列
// g[u][v]:从城市u到城市v的最优航班信息(最小花费,最少经过城市数)
PII g[N][N];
// dis[u]:从起点s到城市u的最小花费和最少经过城市数
PII dis[N];
// SPFA算法:求最短路(按花费优先,花费相同则城市数最少)
void spfa(int s)
{
// 初始化距离为无穷大
memset(dis, 0x3f, sizeof(dis));
queue<int> q; // SPFA队列
dis[s] = {0, 0}; // 起点花费为0,经过城市数为0
q.push(s);
while (!q.empty())
{
int u = q.front(); q.pop(); // 取出队首城市
// 遍历所有可能到达的城市v
for (int v=1; v<=1000; v++)
{
// 计算从u经航班到v的新距离
// 新花费 = 到u的花费 + u到v的航班花费
// 新城市数 = 到u的城市数 + u到v经过的城市数
PII new_dis = {dis[u].first+g[u][v].first, dis[u].second+g[u][v].second};
// 如果新距离更优(花费更小,或花费相同但城市数更少)
if (dis[v]>new_dis)
{
dis[v]=new_dis; // 更新距离
q.push(v); // v入队
}
}
}
}
signed main()
{
cin >> s >> t >> n; // 读入起点、终点和航线数
// 初始化航班矩阵为无穷大
memset(g, 0x3f, sizeof(g));
for (int i=1; i<=n; i++) // 读入每条航线
{
int cost, k; // cost:航班花费, k:航线包含城市数
cin >> cost >> k;
for (int j=1; j<=k; j++) // 读入航线上的城市序列
cin >> a[j];
// 航线上的任意两个城市x<y之间都可以直达
// 花费为cost,经过城市数为y-x(中间经过的城市数)
for (int x=1; x<=k; x++)
for (int y=x+1; y<=k; y++)
// 保留花费最小、花费相同则城市数最少的方案
g[a[x]][a[y]] = min(g[a[x]][a[y]], {cost, y-x});
}
spfa(s); // 从起点s运行SPFA
// 如果终点不可达,输出-1 -1
if (dis[t].first>=1e18) cout << -1 << " " << -1 << endl;
// 否则输出最小花费和最少经过城市数
else cout << dis[t].first << " " << dis[t].second << endl;
return 0;
}
【运行结果】
3 4 3
3 5
1 2 3 4 5
2 3
3 5 4
1 2
1 5
2 2
浙公网安备 33010602011771号