今天做到了这样一道题:最短路径
去搜了搜,发现这其实还是一道比较经典的图论问题:邮递员送信,二者的区别就是这道题是多次,邮递员送信是1次。所以代码中主函数中增加了一个q变量。
这道题目的特点是每次到达一个地方,都要返回起点再出发。很容易就把它归类为最短路径问题。这道题的实现思想就是正反建图。
算法思想:对于所有节点可以拆分成一个一个看。一去一回,去的过程可理解成出发点u到v的最短路径。也就是正向建图。回的过程,将所有的边反向,再计算u到v的最短路径(其实我也不是特别懂这个思想啦~)。
上网找了很多的代码,别人写的没什么问题,完全能够AC。但是对我这种小白来说,不太友好。有个统一的特点就是:没有注释。所以花了好长时间理解,终于最后明白了每一层的含义。
对于图的构建,主要是邻接表的方式。使用一维数组head结合结构体side来表示。正反两个图,很多资料都是将两个分开。但找到一个二维的形式,比较简单。0代表正向,1代表反向。最短路径方法是迪杰特斯拉,使用优先级队列(已经排序,按照first排序)实现。
源代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mamx = 1e5+5;
// 之前有人把这个结构体定义为node,但其实里面信息都是有关于边的
struct side{
// v代表该边的指向
/*
* next代表相同开始边u连接的其他边,使用是邻接表的结构。但其实这个next很有歧义。
* 在Addedge中,next连接的是相同开始边u的不同v,但拼接的时候其实是一个反向更新
*/
int v, next;
ll w; // w代表该边的权重
}a[2][mamx]; // a代表的是边
ll num1, num2; // num1代表正向遍历当前的变数,num2代表反向遍历当前的边数
ll dis[2][mamx]; // dis代表各点据起始点的最短距离
ll head[2][mamx]; // head[][i]代表以i为起点,最新的边序号
bool visit[mamx]; // visit代表该节点是否已经被访问
int n, m;
void Addedge(int i, int u, int v, int w) {
int num;
if (i == 0)
num = num1;
else
num = num2;
// num通过先自增,这样num代表的是最新的边序号
a[i][++num].next = head[i][u]; // 采用倒序方法,当前边的next是上一条的编号
head[i][u] = num; // head[i][u]代表以u为起点的最新的边的编号
a[i][num].v = v;
a[i][num].w = w;
// 更新正向/反向当前的边数
if (i == 0)
num1 = num;
else
num2 = num;
}
/*
* 迪杰特斯拉的实现使用了优先队列,基本还是按照迪杰特斯拉的方法。
* 区别在于使用优先队列,免除了不断寻找最小值的的过程。
*/
void dij(int x) {
for (int i=1; i<=n; i++) {
dis[x][i] = 1e16; // 寻找最小值,传入的值应该尽可能大
visit[i] = 0;
}
priority_queue<pair<ll, int> > q; // ll类型代表权值,int类型是指向
dis[x][1] = 0;
q.push(make_pair(0,1));
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (visit[u] == 1)
continue;
visit[u] = 1;
for (int i=head[x][u]; i; i=a[x][i].next) {
int v = a[x][i].v;
ll w = a[x][i].w;
if (dis[x][v] > dis[x][u] + w) {
dis[x][v] = dis[x][u] + w;
/*
此处传入的是负值,原因在于:我们希望是递增序列,这样从左到右遍历寻找
到的第一个visit[u]为false的就是当前最小的。但优先级队列默认是降序排列
此处加入负号,达到递增目的。
*/
q.push(make_pair(-dis[x][v], v));
}
}
}
}
int main(void) {
int q;
cin >> q;
while (q--) {
memset(head,0,sizeof(head));
cin >> n >> m;
num1 = 0;
num2 = 0;
for (int i=1; i<=m; i++) {
int u, v, w;
cin >> u >> v >> w;
Addedge(0,u,v,w);
Addedge(1,v,u,w);
}
ll ans = 0;
dij(0); // 0代表正向
dij(1); // 1代表反向
for (int i=2; i<=n; i++) { // 自身不计算,从第二个点开始
ans += dis[0][i] + dis[1][i];
}
cout << ans << endl;
}
return 0;
}
参考资料:
https://blog.youkuaiyun.com/JiangHxin/article/details/104059688
https://blog.youkuaiyun.com/weixin_36888577/article/details/79937886