【PAT甲级复习】 专题复习一:最短路径

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 分)

img

考察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;
            }
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值