原文
https://fmcraft.top/index.php/Programming/13.html
主要算法
最短路径快速算法SPFA
最短路径快速算优化SPFAO
优先队列PriorityQueue
题目列表
P1:汽车加油行驶问题(辅垫)aa
题目描述
题目描述
给定一个 N×N 的方形网格,设其左上角为起点◎,坐标为 (1,1) ,X 轴向右为正, Y 轴向下为正,每个方格边长为 1 ,如图所示。
一辆汽车从起点◎出发驶向右下角终点▲,其坐标为 (N,N)。
在若干个网格交叉点处,设置了油库,可供汽车在行驶途中加油。汽车在行驶过程中应遵守如下规则:
汽车只能沿网格边行驶,装满油后能行驶 K 条网格边。出发时汽车已装满油,在起点与终点处不设油库。
汽车经过一条网格边时,若其 X 坐标或 Y 坐标减小,则应付费用 B ,否则免付费用。
汽车在行驶过程中遇油库则应加满油并付加油费用 A。
在需要时可在网格点处增设油库,并付增设油库费用 C (不含加油费用 A )。
N,K,A,B,C 均为正整数, 且满足约束: 2≤N≤100,2≤K≤10。
设计一个算法,求出汽车从起点出发到达终点的一条所付费用最少的行驶路线。
输入
第一行是 N,K,A,B,C的值。
第二行起是一个N×N 的 0-1 方阵,每行 N 个值,至 N+1 行结束。
方阵的第 i 行第 j 列处的值为 1 表示在网格交叉点 (i,j) 处设置了一个油库,为 0 时表示未设油库。各行相邻两个数以空格分隔。
输出
输出所有边的信息(起点的行、列、油到终点的行、列、油和这条边的权值)
注意:必须按照上下左右,先走后加油的顺序进行输出。
样例输入
bash
2 2 10 5 5
0 1
1 0
样例输出
bash
1 1 0 1 1 2 15
1 1 1 2 1 0 0
1 1 1 1 2 0 0
1 1 1 1 1 2 15
1 1 2 2 1 1 0
1 1 2 1 2 1 0
1 2 0 1 2 2 10
1 2 1 1 2 2 10
1 2 2 2 2 1 0
1 2 2 1 1 1 5
2 1 0 2 1 2 10
2 1 1 2 1 2 10
2 1 2 1 1 1 5
2 1 2 2 2 1 0
2 2 0 2 2 2 15
2 2 1 1 2 0 5
2 2 1 2 1 0 5
2 2 1 2 2 2 15
2 2 2 1 2 1 5
2 2 2 2 1 1 5
数据范围
2≤n≤100
2≤k≤10
题解报告
初始化与输入:首先读取输入的参数,包括网格大小 N、油箱容量 K、加油费用 A、行驶费用 B 和增设油库费用 C。接着读取网格信息,判断每个网格点是否有油库。
遍历网格:使用三重循环遍历网格的每个点 (i, j) 和油箱容量 k。外层循环遍历网格的行,中层循环遍历网格的列,内层循环遍历油箱容量。
判断油库:在内层循环中,首先判断当前点是否有油库。如果有油库,并且油箱已满 (k == K),则调用函数 d() 遍历四个方向并输出行驶信息。否则,输出加油信息。
行驶与加油:如果没有油库,则判断油箱是否为空 (k == 0)。如果为空,则不输出行驶信息。否则,调用函数 d() 遍历四个方向并输出行驶信息。如果油箱未满,则输出增设油库信息。
输出结果:根据题目要求,输出所有边的信息,包括起点的行、列、油到终点的行、列、油和这条边的权值。
标准程序
c
#include <bits/stdc++.h>
using namespace std;
const int e[4][3]={{-1,0,1},{1,0,0},{0,-1,1},{0,1,0}},N=110;
int n,K,A,B,C,i,j,k,h,sx,sy,a[N][N];
void d(){
for(h=0;h<4;h++){
sx=i+e[h][0];sy=j+e[h][1];
if(!(sx<1||sx>n||sy<1||sy>n))
cout<<i<<" "<<j<<" "<<k<<" "<<sx<<" "<<sy<<" "<<k-1<<" "<<e[h][2]*B<<"\n";
}
}
int main(){
cin>>n>>K>>A>>B>>C;
for(i=1;i<=n;i++) for(j=1;j<=n;j++) cin>>a[i][j];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=0;k<=K;k++)
if(a[i][j])
if(k==K) d();
else cout<<i<<" "<<j<<" "<<k<<" "<<i<<" "<<j<<" "<<K<<" "<<A<<"\n";
else{
if(k>0) d();
if(k<K) cout<<i<<" "<<j<<" "<<k<<" "<<i<<" "<<j<<" "<<K<<" "<<A+C<<"\n";
}
}
P2:汽车加油行驶问题qiche
题目描述
题目描述
给定一个 N×N 的方形网格,设其左上角为起点◎,坐标为 (1,1) ,X 轴向右为正, Y 轴向下为正,每个方格边长为 1 ,如图所示。
一辆汽车从起点◎出发驶向右下角终点▲,其坐标为 (N,N)。
在若干个网格交叉点处,设置了油库,可供汽车在行驶途中加油。汽车在行驶过程中应遵守如下规则:
汽车只能沿网格边行驶,装满油后能行驶 K 条网格边。出发时汽车已装满油,在起点与终点处不设油库。
汽车经过一条网格边时,若其 X 坐标或 Y 坐标减小,则应付费用 B ,否则免付费用。
汽车在行驶过程中遇油库则应加满油并付加油费用 A。
在需要时可在网格点处增设油库,并付增设油库费用 C (不含加油费用 A )。
N,K,A,B,C 均为正整数, 且满足约束: 2≤N≤100,2≤K≤10。
设计一个算法,求出汽车从起点出发到达终点的一条所付费用最少的行驶路线。
输入
第一行是 N,K,A,B,C的值。
第二行起是一个N×N 的 0-1 方阵,每行 N 个值,至 N+1 行结束。
方阵的第 i 行第 j 列处的值为 1 表示在网格交叉点 (i,j) 处设置了一个油库,为 0 时表示未设油库。各行相邻两个数以空格分隔。
输出
程序运行结束时,输出最小费用。
样例输入 复制
bash
9 3 2 3 6
0 0 0 0 1 0 0 0 0
0 0 0 1 0 1 1 0 0
1 0 1 0 0 0 0 1 0
0 0 0 0 0 1 0 0 1
1 0 0 1 0 0 1 0 0
0 1 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 1
1 0 0 1 0 0 0 1 0
0 1 0 0 0 0 0 0 0
样例输出 复制
bash
12
提示
数据范围:
2≤n≤100
2≤k≤10
题解报告
初始化与输入:首先读取输入的参数,包括网格大小 N、油箱容量 K、加油费用 A、行驶费用 B 和增设油库费用 C。接着读取网格信息,判断每个网格点是否有油库。
遍历网格:使用三重循环遍历网格的每个点 (i, j) 和油箱容量 k。外层循环遍历网格的行,中层循环遍历网格的列,内层循环遍历油箱容量。
判断油库:在内层循环中,首先判断当前点是否有油库。如果有油库,并且油箱已满 (k == K),则调用函数 d() 遍历四个方向并输出行驶信息。否则,输出加油信息
行驶与加油:如果没有油库,则判断油箱是否为空 (k == 0)。如果为空,则不输出行驶信息。否则,调用函数 d() 遍历四个方向并输出行驶信息。如果油箱未满,则输出增设油库信息。
输出结果:根据题目要求,输出最小费用。
标准程序(无记忆化搜索,速度慢)
c
#include <bits/stdc++.h>
using namespace std;
const int e[4][3]={{-1,0,1},{1,0,0},{0,-1,1},{0,1,0}},N=200010,M=110;
int n,K,A,B,C,i,j,k,h,sx,mi,sy,sg,fx,fb,a[M][M],dis[N];
vector <int> f[N],g[N];
priority_queue <pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
int haha(int i,int j,int k){return k*1e4+(i-1)*100+j;}
void d1(){
for(h=0;h<4;h++){
sx=i+e[h][0];sy=j+e[h][1];
if(!(sx<1||sx>n||sy<1||sy>n))
f[haha(i,j,k)].push_back(haha(sx,sy,k-1)),
g[haha(i,j,k)].push_back(e[h][2]*B);
}
}
void d2(int X){
f[haha(i,j,k)].push_back(haha(i,j,K));
g[haha(i,j,k)].push_back(X==1?A:A+C);
}
int main(){
cin>>n>>K>>A>>B>>C;
for(i=1;i<=n;i++) for(j=1;j<=n;j++) cin>>a[i][j];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=0;k<=K;k++)
if(a[i][j]) if(k==K) d1();
else d2(1);
else{
if(k>0) d1();
if(k<K) d2(2);
}
for(i=1;i<=haha(n,n,k);i++) dis[i]=2e9;
dis[haha(1,1,K)]=0;
q.push(make_pair(0,haha(1,1,K)));
while(!q.empty()){
fx=q.top().second;fb=q.top().first;q.pop();
for(i=0;i<f[fx].size();i++){
sx=f[fx][i];sg=g[fx][i];
if(sg+dis[fx]<dis[sx]) dis[sx]=dis[fx]+sg,q.push(make_pair(dis[sx],sx));
}
}
for(mi=2e9,i=0;i<=K;i++) mi=min(mi,dis[haha(n,n,i)]);
cout<<mi;
}
标准程序(记忆化搜索,速度更快)
c
#include <bits/stdc++.h>
using namespace std;
const int e[4][3]={{-1,0,1},{1,0,0},{0,-1,1},{0,1,0}},N=200010,M=110;
int n,K,A,B,C,i,j,k,h,sx,mi,sy,sg,fx,fb,a[M][M],dis[N],vis[N];
vector <int> f[N],g[N];
priority_queue <pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
int haha(int i,int j,int k){return k*1e4+(i-1)*100+j;}
void d1(){
for(h=0;h<4;h++){
sx=i+e[h][0];sy=j+e[h][1];
if(!(sx<1||sx>n||sy<1||sy>n))
f[haha(i,j,k)].push_back(haha(sx,sy,k-1)),
g[haha(i,j,k)].push_back(e[h][2]*B);
}
}
void d2(int X){
f[haha(i,j,k)].push_back(haha(i,j,K));
g[haha(i,j,k)].push_back(X==1?A:A+C);
}
int main(){
cin>>n>>K>>A>>B>>C;
for(i=1;i<=n;i++) for(j=1;j<=n;j++) cin>>a[i][j];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=0;k<=K;k++)
if(a[i][j]) if(k==K) d1();
else d2(1);
else{
if(k>0) d1();
if(k<K) d2(2);
}
for(i=1;i<=haha(n,n,k);i++) dis[i]=2e9,vis[i]=1;
dis[haha(1,1,K)]=vis[haha(1,1,K)]=0;
q.push(make_pair(0,haha(1,1,K)));
while(!q.empty()){
fx=q.top().second;fb=q.top().first;vis[fx]=1;q.pop();
for(i=0;i<f[fx].size();i++){
sx=f[fx][i];sg=g[fx][i];
if(sg+dis[fx]<dis[sx]){
dis[sx]=dis[fx]+sg;
if(vis[sx]==1)
q.push(make_pair(dis[sx],sx)),vis[sx]=0;
}
}
}
for(mi=2e9,i=0;i<=K;i++) mi=min(mi,dis[haha(n,n,i)]);
cout<<mi;
}
P3:EasySSSPsssp
题目描述
题目描述、输入、输出
输入数据给出一个有 N 个节点,M 条边的带权有向图。要求你写一个程序,判断这个有向图中是否存在负权回路。如果从一个点沿着某条路径出发,又回到了自己,而且所经过的边上的权和小于 0,就说这条路是一个负权回路。
如果存在负权回路,只输出一行 −1;如果不存在负权回路,再求出一个点S到每个点的最短路的长度。约定:S 到 S 的距离为 0,如果 S 与这个点不连通,则输出 NoPath 。
样例输入 复制
bash
6 8 1
1 3 4
1 2 6
3 4 -7
6 4 2
2 4 5
3 6 3
4 5 1
3 5 4
样例输出 复制
bash
0
6
4
-3
-2
7
提示
数据范围:
对于全部数据,2≤N≤1000,1≤M≤105,1≤a,b,S≤N,∣c∣≤106。
做这道题时,你不必为超时担心,不必为不会算法担心,但是如此「简单」的题目,你究竟能 AC 么?
解题思路
- 创建一个邻接表来表示有向图。
- 初始化一个距离数组
dist
,将源节点S
的距离设为 0,其他节点的距离设为无穷大。 - 进行 Bellman-Ford 算法的松弛操作,遍历所有边,更新每个节点的距离。
- 检查是否存在负权回路。如果在进行
n-1
次松弛操作后,仍然存在可以更新的距离,则说明存在负权回路。 - 如果存在负权回路,输出
-1
。 - 如果不存在负权回路,则进行一次松弛操作,得到最短路径。
- 遍历所有节点,如果距离为无穷大,则输出
*NoPath*
,否则输出距离
标准程序
c
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
int i,n,m,s,x,y,z,fx,fb,sx,sg,dis[N],vis[N],fl[N],ans[N];
vector <int> f[N],g[N];
priority_queue <pair<int,int>,vector<pair<int,int> > ,greater<pair<int,int> > > q;
void spfa(int st){
int i;
for(i=1;i<=n;i++) dis[i]=2e9,vis[i]=1;dis[st]=vis[st]=0;
q.push(make_pair(0,st));
while(!q.empty()){
fx=q.top().second;fb=q.top().first;vis[fx]=1;q.pop();fl[fx]++;
if(fl[fx]>n) cout<<-1,exit(0);
for(i=0;i<f[fx].size();i++){
sx=f[fx][i];sg=g[fx][i];
if(dis[fx]+sg<dis[sx]){
dis[sx]=dis[fx]+sg;
if(vis[sx]==1) q.push(make_pair(dis[sx],sx));
}
}
}
if(st==s) for(i=1;i<=n;i++) ans[i]=dis[i];
}
signed main(){
cin>>n>>m>>s;
for(i=1;i<=m;i++) cin>>x>>y>>z,
f[x].push_back(y),g[x].push_back(z);
spfa(s);
for(i=1;i<=n;i++) if(fl[i]==0) spfa(i);
for(i=1;i<=n;i++) if(ans[i]==2e9) cout<<"NoPath\n";
else cout<<ans[i]<<"\n";
}
总结
以上几个例题整体围绕最短路径算法展开,通过不同题目练习,让学员掌握最短路径算法在实际问题中的应用,理解算法的优化和实现细节,提升解决复杂图论问题的能力 。