题目链接: 城市间货物运输Ⅲ
学习了代码随想录 Bellman_ford之单源有限最短路 题目要求最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离。那么经过 k 个城市,则共计 k + 1条边,那么最多需要松弛 k + 1次。
每次对每条边松弛时,要使用上次的minDist数组来计算minDist。原因:
- 本题可以有负权回路,说明只要多做松弛,结果是会变的。
- 本题要求最多经过k个节点,对松弛次数是有限制的
如果本题没有 负权回路,则不使用上次的 minDist数组 同样可以实现
- 边的顺序会影响我们每一次拓展的结果
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m,s,t,k;
cin >> n >> m;
vector<vector<int>> edges;
while(m--){
cin >> s >> t >> k;
edges.push_back({s, t, k});
}
cin >> s >> t >> k;
vector<int> minDist(n + 1, INT_MAX);
minDist[s] = 0;
vector<int> minDistLast(n + 1);
for(int i = 1; i <= k + 1; i++){
minDistLast = minDist;
for(auto& edge : edges){
if(minDistLast[edge[0]] != INT_MAX && minDist[edge[1]] > minDistLast[edge[0]] + edge[2]){
minDist[edge[1]] = minDistLast[edge[0]] + edge[2];
}
}
}
if(minDist[t] == INT_MAX){
cout << "unreachable" << endl;
}else{
cout << minDist[t] << endl;
}
return 0;
}
使用队列优化后的 Bellman_ford 算法,关键在于 如何控制松弛 k 次,可以用一个变量 que_size 记录每一轮松弛入队列的所有节点数量。
下一轮松弛的时候,就把队列里 que_size 个节点都弹出来,就是上一轮松弛入队列的节点。
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m,s,t,k;
cin >> n >> m;
vector<list<pair<int, int>>> grid(n + 1);
while(m--){
cin >> s >> t >> k;
grid[s].push_back(make_pair(t, k));
}
cin >> s >> t >> k;
vector<int> minDist(n + 1, INT_MAX);
minDist[s] = 0;
k++;
queue<int> que;
que.push(s);
vector<int> minDistLast(n + 1);
while(k-- && !que.empty()){
minDistLast = minDist;
int nodeNum = que.size();
while(nodeNum--){
int cur = que.front();
que.pop();
for(auto& edge : grid[cur]){
auto [next, val] = edge;
if(minDist[next] > minDistLast[cur] + val){
minDist[next] = minDistLast[cur] + val;
que.push(next);
}
}
}
}
if(minDist[t] == INT_MAX){
cout << "unreachable" << endl;
}else{
cout << minDist[t] << endl;
}
return 0;
}
可以在每轮松弛中,对已经访问过的节点,不再重复加入队列
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m,s,t,k;
cin >> n >> m;
vector<list<pair<int, int>>> grid(n + 1);
while(m--){
cin >> s >> t >> k;
grid[s].push_back(make_pair(t, k));
}
cin >> s >> t >> k;
vector<int> minDist(n + 1, INT_MAX);
minDist[s] = 0;
k++;
queue<int> que;
que.push(s);
vector<int> minDistLast(n + 1);
while(k-- && !que.empty()){
minDistLast = minDist;
int nodeNum = que.size();
vector<bool> visited(n + 1, false); // 每一轮松弛中,控制节点不用重复入队列
while(nodeNum--){
int cur = que.front();
que.pop();
for(auto& edge : grid[cur]){
auto [next, val] = edge;
if(minDist[next] > minDistLast[cur] + val){
minDist[next] = minDistLast[cur] + val;
if(visited[next]) continue; // 不用重复放入队列,但需要重复松弛,所以放在这里位置
visited[next] = true;
que.push(next);
}
}
}
}
if(minDist[t] == INT_MAX){
cout << "unreachable" << endl;
}else{
cout << minDist[t] << endl;
}
return 0;
}