[ABC325E] Our clients, please wait a moment 题解
[ABC325E] Our clients, please wait a moment
题目描述
一个国家里有 \(n\) 个城市。
你需要从 \(1\) 号城市旅行到 \(n\) 号城市。
你有坐车和坐火车两种通行方式,对于从城市 \(i\) 到城市 \(j\) :
- 坐车会花费 \(D_{i,j} \times A\) 分钟
- 坐火车会花费 \(D_{i,j} \times B+C\) 分钟
你可以在不花费任何时间的情况下从坐车切换为坐火车,但不能从坐火车切换为坐车。
问从城市 \(1\) 到城市 \(n\) 最少需要几分钟?
数据范围
- $ 2\ \leq\ N\ \leq\ 1000 $
- $ 1\ \leq\ A,\ B,\ C\ \leq\ 10^6 $
- $ D_{i,j}\ \leq\ 10^6 $
- $ D_{i,i}\ =\ 0 $
- $ D_{i,j}\ =\ D_{j,i}\ >\ 0 $ $ (i\ \neq\ j) $
- 入力される数値はすべて整数。
解法
前缀芝士🧀
Dijkstra 算法,一种求单源最短路径的算法。在算法中,以边权为拓扑序,因为在正权图当中每多增加一条边到最短路径当中,那么边权一定会变大。所以我们总是使用最短的边权,然后转移到相邻的点。如果那一个点转移的更优,那么就需要进行松弛操作,更新最短路径。
由于每次必会取最小值,我们可以进行最优化剪枝——当同一个结点比之前的劣,那么就退出。我们可以发现,拓扑序保证了结点的距离会慢慢增加,所以我们只需要记录这个结点是否出现过就行了,当出现过就直接退出。注意这不是可行性剪枝。
可以使用优先队列来快速求出最小的长度。由于使用了从小到大不断上升的拓扑序,所以不能处理负边权的图,因为这样会使路径长度不断下降,而最优化剪枝也就失去了作用,因此一般会死循环或求出错误的答案。需要遍历 \(n\) 个点和 \(m\) 条边,而优先队列进队列最多 \(n\) 次,因此时间复杂度为 \(\mathcal O((n+m)\log_2 n)\)。
void dijkstra(ll s) {
fill(d + 1, d + n + 1, 1e9); // 初始化距离数组
fill(f + 1, f + n + 1, 0); // 初始化标记数组
for (q.push({s, 0}), d[s] = 0; q.size(); q.pop()) { // 入队并标记。由于拓扑序是上升的,因此可以最后在pop
auto t = q.top(); // 获取队头
if (f[t.to]) { // 如果遍历过了
continue; // 最优性剪枝
}
f[t.to] = 1; // 打上标记
for (auto i : e[t.to]) { // 遍历零点
if (d[t.to] + i.v < d[i.to]) { // 如果距离更近
q.push({i.to, d[i.to] = d[t.to] + i.v}); // 更新并入队
}
}
}
}
思路
根据题目我们可以知道,这题就是求点 \(1\) 到点 \(n\) 的最短距离距离,也就是时间。单源最短路径算法有 Dijkstra 和 SPFA,但是 \(1\le m(n^2)\le 10^6\) 的巨大数据只能够使用时间复杂度为 \(\mathcal O((n+m)\log_2 n)\) 的 Dijkstra 了。因此,解题结束。
但是,你做着做着,欸,不对啊,这里不能从坐火车切换成坐车,那我们怎么办?如果没有这种设定,那么我们就相当于如虎添翼,直接建图然后一甩手怼到 Dijkstra 脸上,然后让他跑完得出答案。但是有了这个设定就很烦躁,就像一阵微风拂过你的脸颊但是又飞过来一根铁柱。
此时你一定非常想杀人,但是要冷静。我们可以像一种方案,首先从 \(1\) 开始,只做车,跑最短路。然后再从 \(n\) 开始,只做船,在跑一遍最短路。跑完后,枚举每一个点,就假设他为 \(i\)。我们求出 \(1\sim i\) 的最短距离,然后再转过头去求 \(i\sim n\) 的最短距离。这样子,两条最短路径就拼了起来,形成了一条完整的最短路径。可谓是妙天下之解法也。
具体实现
我们定义两种 Dijkstra 函数,分别传参 \(s\) 表示起始点位:
dijkstra1:假设每次的边权是 \(x\),那么路径增加的长度就是 \(x\times A\);dijkstra2:假设每次的边权是 \(x\),那么路径增加的长度就是 \(x\times B+C\)。注意 \(i\) 到 \(j\) 的边权应该对应的是 \(D_{(j,i)}\) 而不是 \(D_{(i,j)}\),因为这里跑的是是反向的最短路。
假如第 \(i\) 个点到第 \(j\) 个点的最短路是 \(d_{(i,j)}\),我们只需要最后求一个值 \(\min\limits_{i=1}^n d_{(1,i)}+d_{(n,i)}\),表示连接起来的最短路的和。至此,本题结束。
代码
#include <iostream>
#include <queue>
using namespace std;
using ll = long long;
const ll kMaxN = 1005;
struct Node {
ll to, v;
friend bool operator<(const Node &a, const Node &b) {
return a.v > b.v;
}
} t;
ll e[kMaxN][kMaxN], d[kMaxN][kMaxN], f[kMaxN], n, a, b, c, ans = 1e18;
priority_queue<Node> q;
void dijkstra1(ll s) {
fill(d[s] + 1, d[s] + n + 1, 1e18);
fill(f + 1, f + n + 1, 0);
for (q.push({s, 0}), d[s][s] = 0; q.size(); q.pop()) {
if (f[(t = q.top()).to]) {
continue;
}
f[t.to] = 1;
for (ll i = 1; i <= n; i++) {
if (d[s][t.to] + e[t.to][i] * a < d[s][i]) {
q.push({i, d[s][i] = d[s][t.to] + e[t.to][i] * a});
}
}
}
}
void dijkstra2(ll s) {
fill(d[s] + 1, d[s] + n + 1, 1e18);
fill(f + 1, f + n + 1, 0);
for (q.push({s, 0}), d[s][s] = 0; q.size(); q.pop()) {
if (f[(t = q.top()).to]) {
continue;
}
f[t.to] = 1;
for (ll i = 1; i <= n; i++) {
if (d[s][t.to] + e[i][t.to] * b + c < d[s][i]) {
q.push({i, d[s][i] = d[s][t.to] + e[i][t.to] * b + c});
}
}
}
}
int main() {
cin >> n >> a >> b >> c;
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= n; j++) {
cin >> e[i][j];
}
}
dijkstra1(1);
dijkstra2(n);
for (ll i = 1; i <= n; i++) {
ans = min(ans, d[1][i] + d[n][i]);
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号