文章目录
- 1 无负权边的单源最短路径——Dijkstra
- 2 有负权边的单源最短路径——Bellman-Ford算法和SPFA算法
- 3 全源最短路径——Floyd算法
- 4 例题解析
1 无负权边的单源最短路径——Dijkstra
1.1 邻接矩阵版:适用于V不超过1000的情况,复杂度O( 2 V 2 2V^2 2V2)。
const int MAXV = 1000;
const int INF = 1000000000;
int n, G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV] = false;
void dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
}
}
}
}
}
1.2 邻接表版:复杂度O( V 2 + E V^2 + E V2+E)
const int MAXV = 1000;
const int INF = 1000000000;
struct Node{
int v, dis; //v为边的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV];
bool vis[MAXV] = false;
void dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
//只有下面这个for循环的写法和邻接矩阵不相同
for(int j=0; j<Adj[u].size(); j++){
int v = Adj[u][j].v;
if(!vis[v]){
if(d[v] > d[u] + Adj[u][j].dis){
d[v] = d[u] + Adj[u][j].dis;
}
}
}
}
}
1.3 优先队列优化的Dijkstra
优化思路:寻找最小d[u]的过程可以使用priority_queue来优化时间,这样的话就可以把最终的复杂度降低为O( V l o g V + E VlogV+E VlogV+E)。
const int MAXV = 1000;
const int INF = 1000000000;
typedef pair<int,int> P; //first是最短距离,second是顶点的编号
struct Node{
int v, dis; //v为边的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV];
bool vis[MAXV] = false;
void dijkstra(int s){
priority_queue<P, vector<P>, greater<P> > q;
fill(d, d+MAXV, INF);
d[s] = 0;
q.push(P(0,s));
while(!q.empty())
{
P p = q.top();
q.pop();
int u = p.second; //顶点的编号
if (d[u] < p.first) continue;
for(int j = 0; j < Adj[v].size(); j++)
{
int v = Adj[u][j].v;
if(!vis[v]){
if(d[v] > d[u] + Adj[u][j].dis){
d[v] = d[u] + Adj[u][j].dis;
q.push(P(d[v], v));
}
}
}
}
}
1.4 记录最短路径
只需在前面的代码中添加一个pre数组记录前驱:
int pre[MAXV];
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
pre[v] = u;
}
}
之后再DFS遍历反向输出:
void DFS(int s, int v){
if(v == s){
printf("%d\n",s);
return;
}
DFS(pre[v]);
printf("%d\n",s);
}
1.5 多个标尺如何处理
1.5.1 新增边权
const int MAXV = 1000;
const int INF = 1000000000;
int cost[MAXV][MAXV]; //cost从题目中读取,表示任意两个顶点间的cost
int c[MAXV]; //除了起点为0外,其他初始化为INF
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
c[v] = c[u] + cost[u][v];
}
else if(d[v] == d[u] + G[u][v] && c[v] > c[u] + cost[u][v]){
c[v] = c[u] + cost[u][v];
}
}
}
1.5.2 新增点权
const int MAXV = 1000;
const int INF = 1000000000;
int weight[MAXV]; //表示某个顶点的权值
int w[MAXV]; //表示从起点s到达顶点u能够得到的最大点权,除了起点为weight[s]外,其他初始化为0
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
w[v] = w[u] + weight[v];
}
else if(d[v] == d[u] + G[u][v] && w[v] > w[u] + weight[v]){
w[v] = w[u] + weight[v];
}
}
}
1.5.3 求最短路径条数
const int MAXV = 1000;
const int INF = 1000000000;
int num[MAXV]; //表示从起点s到达顶点u的最短路径条数,起点num[s]初始化为1,其他初始化为0
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
num[v] = num[u];
}
else if(d[v] == d[u] + G[u][v]){
num[v] += num[u];
}
}
}
1.6 多个标尺且比较复杂:Dijkstra + DFS
上面给出的情况都是与“和”有关的,例如边权之和,点权之和,最短路径条数等等,但题目也有可能出现一些更加复杂的计算边权或点权的办法,这个时候直接在Dijkstra算法中操作就有可能得到错误的结果(有可能不满足最优子结构),所以可以使用更通用的,模板化的方法来解决:先用Dijkstra算法求出所有最短路径,然后用DFS遍历每一条路径并计算相应的标尺情况并选出最优。
1.6.1 Dijkstra默写模板
const int MAXV = 1000;
const int INF = 1000000000;
int n, G[MAXV][MAXV];
int d[MAXV];
vector<int> pre[MAXV];
bool vis[MAXV] = false;
void dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(d[v] > d[u] + G[u][v]){
d[v] = d[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[v] == d[u] + G[u][v]){
pre[v].push_back(u);
}
}
}
}
}
1.6.2 DFS遍历所有最短路径模板
需要注意的是,存储在path中的路径是逆序的,因此访问节点需要倒着进行。
int optvalue;
vector<int> pre[MAXV];
vector<int> path, tempPath;
int pathnum = 0; //记录最短路径条数
void DFS(int v){
if(v == st){
tempPath.push_back(st);
int value;
计算路径tempPath上的value值;
if(value > optvalue){
optvalue = value;
path = tempPath;
}
pathnum++;
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i=0; i<pre[v].size; i++){
DFS(pre[v][i]);
}
tempPath.pop_back();
}
2 有负权边的单源最短路径——Bellman-Ford算法和SPFA算法
2.1 BF算法,复杂度 O ( V E ) O(VE) O(VE)。
算法思路是对图中的边进行n-1轮操作,每一轮都遍历图中所有的边,对每条边进行松弛。如果没有源点可达的负环,那么n-1轮操作后数组d中的所有值都应该达到最优。此时再遍历一次所有边,如果还能松弛,说明有源点可达的负环。BF算法如果使用领接矩阵的话,复杂度会上升到 O ( V 2 ) O(V^2) O(V2)。
for(int i=0; i<n-1; i++){
int flag = 0;
for(each edge u->v){
if(d[u] + length[u->v] < d[v]){
d[v] = d[u] + length[u->v];
flag = 1;
}
}
if(flag == 0) break; //如果某一轮操作时发现所有边都没有倍松弛,则直接退出。
}
//如果有原点可达的负环,则返回false。
for(each edge u->v){
if(d[u] + length[u->v] < d[v]){
return false;
}
}
至于最短路径的求解,有多重标尺的做法与Dijkstra一样,唯一要注意的是统计最短路径的做法,由于BF算法期间会多次访问曾经访问过的节点,如果沿用Dijkstra中的做法则会导致重复统计,需要设置记录前驱的set pre[MAXV],当遇到一条和已有最短路径长度相同的路径时,要置num[v]为0,遍历set中保存的前驱,重新统计一遍。
const int MAXV = 1000;
const int INF = 1000000000;
struct Node{
int v, dis; //v为边的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV], num[MAXV];
set<int> pre[MAXV];
void BF(int s){
fill(d, d+MAXV, INF);
fill(num, num+MAXV, 0);
num[s] = 1;
d[s] = 0;
for(int i=0; i<n-1; i++){
for(int u=0; u<n; u++){
for(int j=0; j<Adj[u].size(); j++){
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
if(d[u] + dis < d[v]){
d[v] = d[u] + dis;
num[v] = num[u];
pre[v].clear();
pre[v].insert(u);
}
else if(d[u] + dis == d[v]){
pre[v].insert(u); // 重新统计num[v]
num[v] = 0;
for(auto it = pre[v].begin(); it != pre[v].end(); it++){
num[v] += num[*it];
}
}
}
}
}
}
2.2 SPFA算法,复杂度 O ( k E ) O(kE) O(kE)。
注意到,只有当某个顶点u的d[u]值改变时,从它出发的边的邻接点v的d[v]值才有可能改变,因此可以建立一个队列,每次将队首节点u取出,然后对从u出发的所有邻接边u->v进行松弛操作,如果松弛成功,此时的v如果不在队列中,就将其加入队列。这样操作直到队列为空(说明图中不存在从源点可达的负环),或者是某个顶点的入队次数超过V-1(说明图中存在从源点可达的负环)。如果已经知道了图中不存在可达负环,可以省略innum数组。
如果负环从源点不可达,则需要添加一个辅助顶点C,并添加一条从源点到达C的有向边以及V-1条从C到达除源点外其他顶点的有向边。这样就把原本不可达的负环变得可达。
const int MAXV = 1000;
const int INF = 1000000000;
struct Node{
int v, dis; //v为边的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV], innum[MAXV]; //记录顶点入队次数
bool inq[MAXV]; //顶点是否在队列中
void SPFA(int s){
fill(d, d+MAXV, INF);
fill(innum, innum+MAXV, 0);
fill(inq, inq+MAXV, 0);
queue<int> q;
q.push(s);
inq[s] = 1;
innum[s]++;
d[s] = 0;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = false;
for(int j=0; j<Adj[u].size(); j++){
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
if(d[u] + dis < d[v]){
d[v] = d[u] + dis;
if(!inq[v]){
q.push(v);
inq[v] = 1;
innum[v]++;
if(innum[v] >= n) return false; //有可达负环
}
}
}
}
return true; //无可达负环
}
以上介绍的是BFS版的SPFA,如果我们在找到一条松弛过的边之后,接下来的操作不是将其加入队列,而是直接沿着这条边继续松弛,这就是DFS版的SPFA了。DFS版的SPFA对判环有奇效,**DFS版SPFA判环根据:若一个节点出现2次及以上,则存在负环。**下面的代码只判断负环,所以可以把d[MAXV]数组初始化为0。
const int MAXV = 1000;
const int INF = 1000000000;
struct Node{
int v, dis; //v为边的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV];
bool vis[MAXV]; //存储节点是否出现过
int flag = 0; //是否找到负环
void DFS_SPFA(int u){
fill(d, d+MAXV, 0);
vis[u] = 1;
for(int i=0; i<Adj[u].size(); i++){
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
if(d[u] + dis < d[v]){
if(vis[v] || flag){
flag = 1;
break;
}
d[v] = d[u] + dis;
DFS_SPFA(v);
}
}
vis[u] = 0;
}
void main(){
//每个顶点判断一次负环
for(int i=0; i<n; i++){
DFS_SPFA(i);
if(flag) break;
}
}
3 全源最短路径——Floyd算法
全源最短路问题:对于给定的图G,求任意两点之间的最短路径长度。时间复杂度是 O ( n 3 ) O(n^3) O(n3),所以决定了顶点数n限制在200以内,且使用邻接矩阵来存储图是非常适合使用Floyd算法的。
Floyd算法的思想异常简洁:如果存在顶点k,使得以k作为中介点时顶点i和顶点j的当前最短距离缩短,则使用顶点k作为顶点i和顶点j的中介点。核心代码如下:
void Floyd(){
for(int k=0; k<n; k++){
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
if(dis[i][k] != INF && dis[k][j] != INF && dis[i][k] + dis[k][j] < dis[i][j]){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}
int main(){
fill(dis[0], dis[0]+MAXV*MAXV, INF);
for(int i=0; i<n; i++){
dis[i][i] = 0; //每个顶点到自身的距离初始化为0
}
cin >> u >> v >> w;
dis[u][v] = w; //输入u到v的距离为w
Floyd(); //调用Floyd()算法
输出dis数组即可。
}
4 例题解析
4.1 A1003 Emergency (25 分)
模板题,求最短路径条数和最大点权和。
4.1.1 常规Dijkstra
//常规Dijkstra
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 505;
const int INF = 1000000000;
int n, m, c1, c2;
int weight[MAXV];
int G[MAXV][MAXV];
int pathnum[MAXV], w[MAXV], d[MAXV];
bool vis[MAXV];
void Dijkstra(int s){
fill(d, d+MAXV, INF);
pathnum[s] = 1;
w[s] = weight[s];
d[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(G[u][v] + d[u] < d[v]){
d[v] = G[u][v] + d[u];
pathnum[v] = pathnum[u];
w[v] = w[u] + weight[v];
}
else if(G[u][v] + d[u] == d[v]){
pathnum[v] += pathnum[u];
if(w[v] < w[u] + weight[v]){
w[v] = w[u] + weight[v];
}
}
}
}
}
return;
}
int main(){
fill(G[0], G[0]+MAXV*MAXV, INF);
int a, b, len;
cin >> n >> m >> c1 >> c2;
for(int i=0; i<n; i++){
scanf("%d",&weight[i]);
}
for(int i=0; i<m; i++){
scanf("%d %d %d",&a, &b, &len);
G[a][b] = G[b][a] = len;
}
Dijkstra(c1);
printf("%d %d",pathnum[c2], w[c2]);
}
4.1.2 优先队列优化的Dijkstra
//优先队列优化的Dijkstra
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> p;
const int MAXV = 505;
const int INF = 1000000000;
int n, m, c1, c2;
int weight[MAXV];
int G[MAXV][MAXV];
int pathnum[MAXV], w[MAXV], d[MAXV];
bool vis[MAXV];
void Dijkstra(int s){
fill(d, d+MAXV, INF);
pathnum[s] = 1;
w[s] = weight[s];
d[s] = 0;
priority_queue<p, vector<p>, greater<p> > q;
q.push(p(0,s));
while(!q.empty()){
int u = q.top().second;
q.pop();
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(G[u][v] + d[u] < d[v]){
d[v] = G[u][v] + d[u];
pathnum[v] = pathnum[u];
w[v] = w[u] + weight[v];
q.push(p(d[v],v));
}
else if(G[u][v] + d[u] == d[v]){
pathnum[v] += pathnum[u];
if(w[v] < w[u] + weight[v]){
w[v] = w[u] + weight[v];
}
}
}
}
}
return;
}
int main(){
fill(G[0], G[0]+MAXV*MAXV, INF);
int a, b, len;
cin >> n >> m >> c1 >> c2;
for(int i=0; i<n; i++){
scanf("%d",&weight[i]);
}
for(int i=0; i<m; i++){
scanf("%d %d %d",&a, &b, &len);
G[a][b] = G[b][a] = len;
}
Dijkstra(c1);
printf("%d %d",pathnum[c2], w[c2]);
}
4.1.3 DFS + Dijkstra
//DFS + Dijkstra
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> p;
const int MAXV = 505;
const int INF = 1000000000;
int n, m, c1, c2;
int weight[MAXV];
int G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV];
int pathnum = 0, maxWeight = -1;
vector<int> tempPath, ansPath;
vector<int> pre[MAXV]; //记录节点前驱
void Dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
priority_queue<p, vector<p>, greater<p> > q;
q.push(p(0,s));
while(!q.empty()){
int u = q.top().second;
q.pop();
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(G[u][v] + d[u] < d[v]){
d[v] = G[u][v] + d[u];
pre[v].clear();
pre[v].push_back(u);
q.push(p(d[v],v));
}
else if(G[u][v] + d[u] == d[v]){
pre[v].push_back(u);
}
}
}
}
return;
}
void DFS(int v){
if(v == c1){
tempPath.push_back(v);
pathnum++;
int tempVal = 0;
for(int j=tempPath.size()-1; j>=0; j--){
tempVal += weight[tempPath[j]];
}
if(tempVal > maxWeight){
maxWeight = tempVal;
ansPath = tempPath;
}
tempPath.pop_back();
return;
}
else{
tempPath.push_back(v);
for(int i=0; i<pre[v].size(); i++){
DFS(pre[v][i]);
}
tempPath.pop_back();
}
return;
}
int main(){
fill(G[0], G[0]+MAXV*MAXV, INF);
int a, b, len;
cin >> n >> m >> c1 >> c2;
for(int i=0; i<n; i++){
scanf("%d",&weight[i]);
}
for(int i=0; i<m; i++){
scanf("%d %d %d",&a, &b, &len);
G[a][b] = G[b][a] = len;
}
Dijkstra(c1);
DFS(c2);
printf("%d %d",pathnum, maxWeight);
}
4.1.4 SPFA
//SPFA
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 505;
const int INF = 1000000000;
struct Node{
int v, dis;
};
int n, m, c1, c2;
int weight[MAXV];
vector<Node> Adj[MAXV];
unordered_set<int> pre[MAXV];
int d[MAXV];
bool inq[MAXV];
int pathnum[MAXV], w[MAXV];
void SPFA(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
pathnum[s] = 1;
w[s] = weight[s];
queue<int> q;
q.push(s);
inq[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = 0;
for(int i=0; i<Adj[u].size(); i++){
int v = Adj[u][i].v;
int dis = Adj[u][i].dis;
if(d[u] + dis < d[v]){
d[v] = d[u] + dis;
pre[v].clear();
pre[v].insert(u);
pathnum[v] = pathnum[u];
w[v] = w[u] + weight[v];
if(!inq[v]){
q.push(v);
inq[v] = 1;
}
}
else if(d[u] + dis == d[v]){
pre[v].insert(u);
pathnum[v] = 0;
for(auto it = pre[v].begin(); it != pre[v].end(); it++){
pathnum[v] += pathnum[*it];
}
if(w[v] < w[u] + weight[v]){
w[v] = w[u] + weight[v];
}
if(!inq[v]){
q.push(v);
inq[v] = 1;
}
}
}
}
return;
}
int main(){
int a, b, len;
cin >> n >> m >> c1 >> c2;
for(int i=0; i<n; i++){
scanf("%d",&weight[i]);
}
for(int i=0; i<m; i++){
scanf("%d %d %d",&a, &b, &len);
Adj[a].push_back({b,len});
Adj[b].push_back({a,len});
}
SPFA(c1);
printf("%d %d",pathnum[c2], w[c2]);
}
4.2 A1018 Public Bike Management (30 分)
此题计算第二标尺的过程比较复杂,有不满足最优子结构的危险在,故直接选用Dijkstra+DFS解决。
//DFS + Dijkstra
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 505;
const int INF = 1000000000;
int bikeNum[MAXV], d[MAXV];
int G[MAXV][MAXV];
bool vis[MAXV];
vector<int> pre[MAXV];
vector<int> tempPath, ansPath;
int minTakeBack = INF, minSend = INF;
int cmax, n, sp, m;
void Dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
for(int i=0; i<n+1; i++){
int u = -1, MIN = INF;
for(int j=0; j<n+1; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n+1; v++){
if(!vis[v] && G[u][v] != INF){
if(d[u] + G[u][v] < d[v]){
d[v] = d[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u] + G[u][v] == d[v]){
pre[v].push_back(u);
}
}
}
}
return;
}
void DFS(int v){
if(v == 0){
tempPath.push_back(0);
int take = 0, send = 0, capacity = cmax / 2;
int carry = 0;
for(int i=tempPath.size()-2; i>=0; i--){
if(carry + bikeNum[tempPath[i]] - capacity < 0){
send += capacity - bikeNum[tempPath[i]] - carry;
carry = 0;
}
else if(carry + bikeNum[tempPath[i]] - capacity == 0) carry = 0;
else{
carry = carry + bikeNum[tempPath[i]] - capacity;
}
}
take = carry;
if(send < minSend || send == minSend && take < minTakeBack){
minTakeBack = take;
minSend = send;
ansPath = tempPath;
}
tempPath.pop_back();
}
else{
tempPath.push_back(v);
for(int i=0; i<pre[v].size(); i++){
DFS(pre[v][i]);
}
tempPath.pop_back();
}
return;
}
int main(){
int a, b, len;
fill(G[0], G[0]+MAXV*MAXV, INF);
cin >> cmax >> n >> sp >> m;
for(int i=1; i<=n; i++){
scanf("%d",&bikeNum[i]);
}
for(int i=0; i<m; i++){
scanf("%d %d %d",&a, &b, &len);
G[a][b] = G[b][a] = len;
}
Dijkstra(0);
DFS(sp);
printf("%d ",minSend);
for(int j=ansPath.size()-1; j>=0; j--){
printf("%d",ansPath[j]);
if(j > 0) printf("->");
}
printf(" %d",minTakeBack);
}
4.3 A1030 Travel Plan (30 分)
第二标尺为边权,符合最优子结构,直接用Dijkstra解决即可。
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 505;
const int INF = 1000000000;
int n, m, s, ed;
int G[MAXV][MAXV], C[MAXV][MAXV];
int d[MAXV], c[MAXV];
bool vis[MAXV];
int pre[MAXV];
void Dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
c[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(G[u][v] + d[u] < d[v] || G[u][v] + d[u] == d[v] && c[v] > C[u][v] + c[u]){
d[v] = G[u][v] + d[u];
c[v] = C[u][v] + c[u];
pre[v] = u;
}
}
}
}
return;
}
void DFS(int v){
if(v == s){
printf("%d ",s);
return;
}
DFS(pre[v]);
printf("%d ",v);
}
int main(){
int a,b,dis,cost;
fill(G[0], G[0]+MAXV*MAXV, INF);
fill(C[0], C[0]+MAXV*MAXV, INF);
cin >> n >> m >> s >> ed;
for(int i=0; i<m; i++){
scanf("%d %d %d %d",&a,&b,&dis,&cost);
G[a][b] = G[b][a] = dis;
C[a][b] = C[b][a] = cost;
}
Dijkstra(s);
DFS(ed);
printf("%d %d",d[ed],c[ed]);
}
4.4 A1072 Gas Station (30 分)
计算多个最短路径,仍然使用常规Dijkstra即可解决,需要注意的是下标转换要使用stoi(),因为G10的出现,不能用 str[1] - ‘0’ !!
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 1020;
const int INF = 1000000000;
int n, m, k, ds;
int G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV];
int ansdis = -1, anssum = INF, ansIdx = -1;
void Dijkstra(int s){
fill(d, d+MAXV, INF);
fill(vis, vis+MAXV, false);
d[s] = 0;
for(int i=0; i<n+m; i++){
int u = -1, MIN = INF;
for(int j=1; j<=n+m; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=1; v<=n+m; v++){
if(!vis[v] && G[u][v] + d[u] < d[v]){
d[v] = G[u][v] + d[u];
}
}
}
return;
}
int main(){
fill(G[0], G[0]+MAXV*MAXV, INF);
string stra, strb;
bool flag = false;
int dis, a, b;
cin >> n >> m >> k >> ds;
for(int i=0; i<k; i++){
cin >> stra >> strb >> dis;
if(stra[0] == 'G') a = n + stoi(stra.substr(1));
else a = stoi(stra);
if(strb[0] == 'G') b = n + stoi(strb.substr(1));
else b = stoi(strb);
G[a][b] = G[b][a] = dis;
}
for(int i=n+1; i<=n+m; i++){
Dijkstra(i);
int j = 1, mindis = INF, sum = 0;
for(; j<=n; j++){
if(d[j] > ds) break;
if(d[j] < mindis) mindis = d[j];
sum += d[j];
}
if(j == n+1){
flag = true;
if(mindis > ansdis || mindis == ansdis && sum < anssum){
ansdis = mindis;
anssum = sum;
ansIdx = i;
}
}
}
double ans1, ans2;
ans1 = ansdis;
ans2 = (double)anssum / (double)n;
if(anssum == INF) printf("No Solution\n");
else{
printf("G%d\n",ansIdx-n);
printf("%.1f %.1f\n", ans1, ans2);
}
}
4.5 A1087 All Roads Lead to Rome (30 分)
由于有一个平均幸福值最高的条件,有可能不满足最优子结构,所以直接Dijkstra+DFS解决:
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 205;
const int INF = 1000000000;
int n, m;
int G[MAXV][MAXV];
unordered_map<string, int> cityToIdx;
unordered_map<int, string> idxToCity;
int cityHappy[MAXV], d[MAXV];
vector<int> pre[MAXV];
vector<int> tempPath, ansPath;
int idx = 1;
bool vis[MAXV];
int ansnum = 0, maxHappy = -1, maxAver = -1;
void Dijkstra(int s){
fill(d, d+MAXV, INF);
d[s] = 0;
for(int i=0; i<n; i++){
int u = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!vis[j] && d[j] < MIN){
MIN = d[j];
u = j;
}
}
if(u == -1) return;
vis[u] = 1;
for(int v=0; v<n; v++){
if(!vis[v] && G[u][v] != INF){
if(G[u][v] + d[u] < d[v]){
d[v] = G[u][v] + d[u];
pre[v].clear();
pre[v].push_back(u);
}
else if(G[u][v] + d[u] == d[v]){
pre[v].push_back(u);
}
}
}
}
return;
}
void DFS(int v){
if(v == 0){
ansnum++;
tempPath.push_back(0);
int nowhappy = 0, nowaver = 0;
for(int j=tempPath.size()-2; j>=0; j--){
nowhappy += cityHappy[tempPath[j]];
}
nowaver = nowhappy / (tempPath.size()-1);
if(nowhappy > maxHappy || nowhappy == maxHappy && nowaver > maxAver){
maxHappy = nowhappy;
maxAver = nowaver;
ansPath = tempPath;
}
tempPath.pop_back();
return;
}
else{
tempPath.push_back(v);
for(int i=0; i<pre[v].size(); i++){
DFS(pre[v][i]);
}
tempPath.pop_back();
}
return;
}
int main(){
fill(G[0], G[0]+MAXV*MAXV, INF);
string city, tarcity;
int happy, cost;
cin >> n >> m >> city;
cityToIdx[city] = 0;
idxToCity[0] = city;
for(int i=1; i<n; i++){
cin >> city >> happy;
if(cityToIdx[city] == 0){
cityToIdx[city] = idx;
idxToCity[idx] = city;
idx++;
}
cityHappy[cityToIdx[city]] = happy;
}
for(int i=1; i<=m; i++){
cin >> city >> tarcity >> cost;
int a = cityToIdx[city];
int b = cityToIdx[tarcity];
G[a][b] = G[b][a] = cost;
}
Dijkstra(0);
DFS(cityToIdx["ROM"]);
printf("%d %d %d %d\n",ansnum, d[cityToIdx["ROM"]], maxHappy, maxAver);
for(int i=ansPath.size()-1; i>0; i--){
printf("%s->", idxToCity[ansPath[i]].c_str());
}
printf("ROM");
}
4.6 A1111 Online Map (30 分)
这题需要使用两个Dijkstra,并且保存对应的最短路径进行比较输出:
#include<bits/stdc++.h>
using namespace std;
const int INF = 1000000000;
int n, m;
struct Node{
int v, len, tim;
Node(int a, int b, int c){
v = a;
len = b;
tim = c;
}
};
vector<Node> G[505];
int st, ed;
bool visit[505] = {0};
int d[505] = {0};
int cost[505] = {0};
int pre_d[505], pre_t[505];
int internum[505] = {0};
string ans_t, ans_d;
void Dijkstra_d(int s){
fill(d, d+505, INF);
fill(cost, cost+505, INF);
fill(pre_d, pre_d+505, -1);
d[s] = 0;
cost[s] = 0;
for(int i=0; i<n; i++){
int x = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!visit[j] && d[j] != INF){
if(d[j] < MIN){
MIN = d[j];
x = j;
}
}
}
if(x == -1) return;
visit[x] = 1;
for(int j=0; j<G[x].size(); j++){
Node neighber = G[x][j];
if(!visit[neighber.v]){
if(d[neighber.v] > d[x] + neighber.len || (d[neighber.v] == d[x] + neighber.len && cost[neighber.v] > cost[x] + neighber.tim)){
d[neighber.v] = d[x] + neighber.len;
cost[neighber.v] = cost[x] + neighber.tim;
pre_d[neighber.v] = x;
}
}
}
}
}
void DFS_d(int st, int ed, int acl_ed){
if(ed == st){
ans_d += to_string(st) + " -> ";
return;
}
if(ed >= 0)
DFS_d(st, pre_d[ed], acl_ed);
if(ed != acl_ed) ans_d += to_string(ed) + " -> ";
else ans_d += to_string(ed);
}
void DFS_t(int st, int ed, int acl_ed){
if(ed == st){
ans_t += to_string(st) + " -> ";
return;
}
if(ed >= 0)
DFS_t(st, pre_t[ed], acl_ed);
if(ed != acl_ed) ans_t += to_string(ed) + " -> ";
else ans_t += to_string(ed);
}
void Dijkstra_t(int s){
fill(visit, visit+505, 0);
fill(cost, cost+505, INF);
fill(pre_t, pre_t+505, -1);
cost[s] = 0;
for(int i=0; i<n; i++){
int x = -1, MIN = INF;
for(int j=0; j<n; j++){
if(!visit[j] && cost[j] != INF){
if(cost[j] < MIN){
MIN = cost[j];
x = j;
}
}
}
if(x == -1) return;
visit[x] = 1;
for(int j=0; j<G[x].size(); j++){
Node neighber = G[x][j];
if(!visit[neighber.v]){
if(cost[neighber.v] > cost[x] + neighber.tim || (cost[neighber.v] == cost[x] + neighber.tim && internum[neighber.v] > internum[x] + 1)){
cost[neighber.v] = cost[x] + neighber.tim;
internum[neighber.v] = internum[x] + 1;
pre_t[neighber.v] = x;
}
}
}
}
}
int main(){
int v1, v2, oneway, t, length;
scanf("%d %d",&n,&m);
for(int i=0; i<m; i++){
scanf("%d %d %d %d %d",&v1,&v2,&oneway,&length,&t);
G[v1].push_back(Node(v2,length,t));
if(oneway != 1) G[v2].push_back(Node(v1,length,t));
}
scanf("%d %d",&st,&ed);
Dijkstra_d(st);
Dijkstra_t(st);
DFS_d(st, ed, ed);
DFS_t(st, ed, ed);
if(ans_d == ans_t) printf("Distance = %d; Time = %d: %s",d[ed],cost[ed],ans_t.c_str());
else{
printf("Distance = %d: %s\n",d[ed], ans_d.c_str());
printf("Time = %d: %s\n",cost[ed], ans_t.c_str());
}
}
4.7 A1131 Subway Map (30 分)
“dijkstra是bfs的升级版,就是说如果求最短路径,当图从无权值变成有权值时,bfs不再适用了,于是我们用dijkstra方法。换句话说,对于无权值图,dijkstra方法跟bfs是一致的。你可以画个无权图,用dijkstra走一遍,发现其实这就是bfs。”
这题的一个小trick是在存储任意两个站之间的路径属于几号线时,开line [ 10000 ] [ 10000 ] [10000][10000] [10000][10000]的数组会内存超限(实际测试int x [ 23000 ] [ 23000 ] x[23000][23000] x[23000][23000]= {0}是64M内存要求下的极限了),可以考虑把前一个站台作为高四位,后一个站台作为低四位,用unordered_map<int, int>进行存储,也即line[stop[0] * 10000 + stop[1]] = 1;
// DFS + Dijkstra + 优先队列优化
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10005;
const int INF = 1000000000;
typedef pair<int, int> p;
vector<int> G[MAXN];
unordered_map<int, int> line;
unordered_set<int> stops;
vector<int> pre[MAXN];
vector<int> tempPath, ansPath, ansTstop, ansLine;
int n, m, k, st, ed;
int minTransfer = INF;
int d[MAXN];
bool vis[MAXN];
void Dijkstra(int s){
fill(d, d+MAXN, INF);
fill(vis, vis+MAXN, false);
priority_queue<p, vector<p>, greater<p> > q;
q.push(p(0,s));
d[s] = 0;
int stopnum = stops.size();
while(!q.empty()){
p tempP = q.top();
q.pop();
int u = tempP.second;
// int u = -1, MIN = INF;
// for(auto it = stops.begin(); it != stops.end(); it++){
// int j = *it;
// if(!vis[j] && d[j] < MIN){
// MIN = d[j];
// u = j;
// }
// }
// if(u == -1) return;
vis[u] = 1;
for(int j=0; j<G[u].size(); j++){
int v = G[u][j];
if(!vis[v]){
if(d[u] + 1 < d[v]){
d[v] = d[u] + 1;
pre[v].clear();
pre[v].push_back(u);
q.push(p(d[v], v));
}
else if(d[u] + 1 == d[v]){
pre[v].push_back(u);
q.push(p(d[v], v));
}
}
}
}
return;
}
void DFS(int v){
if(v == st){
tempPath.push_back(st);
int tempTransfer = 0;
vector<int> tempTstop, tempLine;
if(tempPath.size() >= 3){
int j = tempPath.size() - 1;
int preline = line[tempPath[j]*10000 + tempPath[j-1]];
for(j=tempPath.size()-3; j>=0; j--){
int nowline = line[tempPath[j]*10000 + tempPath[j+1]];
if(preline != nowline){
tempTransfer++;
tempTstop.push_back(tempPath[j+1]);
tempLine.push_back(nowline);
}
preline = nowline;
}
}
if(tempTransfer < minTransfer){
minTransfer = tempTransfer;
ansPath = tempPath;
ansTstop = tempTstop;
ansLine = tempLine;
}
tempPath.pop_back();
}
else{
tempPath.push_back(v);
for(int i=0; i<pre[v].size(); i++){
DFS(pre[v][i]);
}
tempPath.pop_back();
}
return;
}
int main(){
scanf("%d",&n);
for(int i=1; i<=n; i++){
int last, temp;
scanf("%d %d",&m,&last);
stops.insert(last);
for(int j=1; j<m; j++){
scanf("%d",&temp);
stops.insert(temp);
G[last].push_back(temp);
G[temp].push_back(last);
line[last*10000 + temp] = line[temp*10000 + last] = i;
last = temp;
}
}
scanf("%d",&k);
for(int i=0; i<k; i++){
scanf("%d %d",&st,&ed);
tempPath.clear();
minTransfer = INF;
Dijkstra(st);
DFS(ed);
printf("%d\n",d[ed]);
int startLine = line[st * 10000 + ansPath[ansPath.size()-2]];
if(ansTstop.size() == 0) printf("Take Line#%d from %04d to %04d.\n",startLine, st, ed);
else{
printf("Take Line#%d from %04d to %04d.\n",startLine, st, ansTstop[0]);
int j = 1;
for(; j<ansTstop.size(); j++){
printf("Take Line#%d from %04d to %04d.\n", ansLine[j-1], ansTstop[j-1], ansTstop[j]);
}
printf("Take Line#%d from %04d to %04d.\n",ansLine[j-1], ansTstop[j-1], ed);
}
}
}
4.8 2021春PAT甲级真题T4 Recycling of Shared Bicycles (30 分)
考察Floyd算法的典型例题!
#include<bits/stdc++.h>
using namespace std;
const int INF = 1000000000;
const int MAXV = 205;
int d[MAXV][MAXV];
int n, m;
bool vis[MAXV] = {0};
int sum = 0;
void Floyd(){
for(int v=0; v<=n; v++){
for(int i=0; i<=n; i++){
for(int j=0; j<=n; j++){
if(d[i][v] + d[v][j] < d[i][j]){
d[i][j] = d[i][v] + d[v][j];
}
}
}
}
}
int main(){
int a, b, dis;
scanf("%d %d",&n,&m);
fill(d[0], d[0] + MAXV * MAXV, INF);
for(int i=0; i<n; i++) d[i][i] = 0;
for(int i=0; i<m; i++){
scanf("%d %d %d",&a,&b,&dis);
d[a][b] = d[b][a] = dis;
}
Floyd();
vector<int> path;
int begin = 0, next;
path.push_back(0);
vis[0] = 1;
while(1){
int MIN = INF;
for(int i=0; i<=n; i++){
if(!vis[i] && d[begin][i] < MIN){
MIN = d[begin][i];
next = i;
}
}
if(MIN == INF){
int flag = false;
printf("0");
for(int j=1; j<path.size(); j++) printf(" %d",path[j]);
printf("\n");
for(int j=0; j<=n; j++){
if(!vis[j]){
if(!flag){
flag = 1;
printf("%d",j);
}
else printf(" %d",j);
}
}
}
else{
path.push_back(next);
vis[next] = 1;
sum += d[begin][next];
begin = next;
if(path.size() == n+1){
printf("0");
for(int j=1; j<path.size(); j++){
printf(" %d",path[j]);
}
printf("\n%d",sum);
break;
}
}
}
}