最短路径(DIJ)——正反建图/邮递员送信

  今天做到了这样一道题:最短路径
  去搜了搜,发现这其实还是一道比较经典的图论问题:邮递员送信,二者的区别就是这道题是多次,邮递员送信是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.csdn.net/JiangHxin/article/details/104059688
https://blog.csdn.net/weixin_36888577/article/details/79937886

猜你喜欢

转载自blog.csdn.net/gls_nuaa/article/details/114856080
今日推荐