7-8 哈利·波特的考试,7-9 旅游规划,7-10 公路村村通
之所以把 7-8,7-9,7-10 放到一起,是因为这三个题都是非常经典的图算法,在之前总结的算法笔记中已经提到过,这里只是做一个复习,所以整理到一起。
发现自己的记忆力是真的不行,还是需要勤加努力多认真学习才能把这些算法都掌握住。
7-9 哈利波特的考试
这道题目是想考全局最短路径,Floyd 算法,直接使用 Floyd 算法即可。
关于 Floyd 算法主要是用来求全局最短路径,我们这里给出一份 Floyd算法 的伪代码:
枚举顶点 k 属于 [1, n]
以顶点 k 作为中介点,枚举所有顶点对 i 和 j
如果 dis[i][k] + dis[k][j] < dis[i][j]
赋值 dis[i][j] = dis[i][k] + dis[k][j]
但是个人觉得这道题目的难度在于读题 TAT,我读题读了半才读懂是什么意思,看来还是需要提高阅读能力。
完整的解题过程如下:
/*
Author: Veeupup
哈利波特的考试
*/
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <climits>
using namespace std;
const int maxn = 110, INF = 1e5;
int n, m;
int dis[maxn][maxn];
// 朴素的 floyd 算法,求全局最短路径
void floyd() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
int main()
{
freopen("data.txt","r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{ // 初始化点之间的距离,自己到自己距离为 0
for (int j = 1; j <= n; j++)
{
if(i == j) dis[i][j] = 0;
else dis[i][j] = INF;
}
}
int v1, v2, weight;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &v1, &v2, &weight);
dis[v1][v2] = dis[v2][v1] = weight;
}
floyd();
int ans = INF;
int cnt = 0;
for (int i = 1; i <= n; i++)
{
int Max = 0;
for (int j = 1; j <= n; j++)
{
if(dis[i][j] == INF)
{ // 如果有孤立的结点,那么就无法完成变形
printf("0");
return 0;
}
Max = max(Max, dis[i][j]);
}
if(Max < ans)
{
ans = Max;
cnt = i;
}
}
printf("%d %d", cnt, ans);
return 0;
}
7-8 旅游规划
这道题是很经典的具有多个标尺的单源最短路径的变形,我们使用 Dijkstra + DFS 来实现多个标尺的计算,不用在 Dijkstra 中将原算法修改的很复杂。
关于 Dijkstra + DFS 可以参考这里的文章来进行复习。
下面给出完整代码实现:
/*
Author: Veeupup
旅游规划
单源最短路径 Dijkstra,有两个标尺 1. 距离 2. 过路费
典型的单源最短路径的变形
*/
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <vector>
using namespace std;
const int maxn = 510, INF = 1e4;
int N, M, S, D;
int G[maxn][maxn]; // 保存距离,第一标尺
int cost[maxn][maxn]; // 保存收费金额,图的边权
int dis[maxn], minCost = INF; // 记录起点到某点的最小距离
bool vis[maxn] = {false}; // 记录是否访问过
vector<int> pre[maxn]; // 保存前一个结点
vector<int> tempPath, path;
void Initial()
{ // 初始化距离和花费最大
fill(G[0], G[0] + maxn * maxn, INF);
fill(cost[0], cost[0] + maxn * maxn, INF);
}
void Dijkstra(int S) {
fill(dis, dis+maxn, INF); // 初始化到所有点距离无限大
dis[S] = 0; // 到自己距离为 0
for (int i = 0; i < N; i++)
{
int u = -1, MIN = INF;
for (int j = 0; j < N; j++)
{ // 寻找未访问的距离最小值
if(vis[j] == false && dis[j] < MIN) {
u = j;
MIN = dis[j];
}
}
if(u == -1) break; // 所有可以到达的点都访问过了
vis[u] = true; // 设置为已访问
for (int v = 0; v < N; v++)
{ // 遍历能从 u 出发到达的所有点
// 如果经过 u 到达 v 更近,那么就更新 v
if(dis[u] + G[u][v] < dis[v]) {
dis[v] = dis[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u); // 经过 u 能够更近的到达 v
}else if(dis[u] + G[u][v] == dis[v]) {
// 如果经过 u 到达的时候距离一样远,那么增加到其前面去
pre[v].push_back(u);
}
}
}
}
void DFS(int V) {
if(V == S) {
tempPath.push_back(S);
int tempCost = 0;
int nowId, nextId;
for (int i = tempPath.size()-1; i > 0; i--)
{
nowId = tempPath[i];
nextId = tempPath[i-1];
tempCost += cost[nowId][nextId];
}
if(tempCost < minCost) {
minCost = tempCost;
path = 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();
}
}
int main()
{
freopen("data.txt","r", stdin);
Initial();
scanf("%d%d%d%d", &N, &M, &S, &D);
int u, v;
for (int i = 0; i < M; i++)
{ // 读取图信息
scanf("%d%d", &u, &v);
scanf("%d%d", &G[u][v], &cost[u][v]);
G[v][u] = G[u][v];
cost[v][u] = cost[u][v];
}
Dijkstra(S);
DFS(D); // 从终点向前回溯
printf("%d %d\n", dis[D], minCost);
return 0;
}
7-10 公路村村通
这道题是非常经典的最小生成树算法,推荐使用kruskal算法。
这个题没什么特殊的地方,掌握好最小生成树算法即可,又复习了一遍图算法吧。
完整代码如下:
/*
Author: Veeupup
公路村村通
最小生成树算法:
Prim(类似Dijkstra) + Krusktal(边贪心)
这里选择使用 Krusktal 算法来实现
算法思想很简单
*/
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <algorithm>
using namespace std;
const int maxn = 1010, INF = INT32_MAX;
int N, M;
struct edge
{
int u, v;
int cost; // 边权
} E[maxn * 3];
bool cmp(edge a, edge b)
{
return a.cost < b.cost;
}
// 使用并查集
int father[maxn];
int findFather(int x)
{
while (x != father[x])
{
x = father[x];
}
return x;
}
// n 为顶点个数, m 为图的边数
int kruskal(int n, int m)
{
// ans 为所求边权之和
int ans = 0, Num_edge = 0;
for (int i = 1; i <= n; i++)
{
father[i] = i;
} // 并查集初始化
sort(E, E + m, cmp);
for (int i = 0; i < m; i++)
{
int faU = findFather(E[i].u);
int faV = findFather(E[i].v);
if (faU != faV)
{
father[faU] = faV;
ans += E[i].cost;
Num_edge++;
if (Num_edge == n - 1)
break;
}
}
if (Num_edge != n - 1)
return -1;
return ans;
}
int main()
{
freopen("data.txt", "r", stdin);
scanf("%d%d", &N, &M);
int u, v, w;
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].cost);
}
int ans = kruskal(N, M);
printf("%d", ans);
return 0;
}
希望对大家有帮助。