首页 > 编程语言 >单源次短路算法 学习笔记

单源次短路算法 学习笔记

时间:2023-08-10 14:57:11浏览次数:54  
标签:tmp dist int 短路 路径 单源次 算法 dis

次短路:顾名思义就是一张图中第二短的路径。
分类:1. 边不可重复经过的次短路问题。边可重复经过的次短路问题。
2. 严格次短路(次短路长度必须大于最短路长度)。非严格次短路(次短路长度可以大于或等于最短路长度)。

一 、边不可重复经过的次短路问题

例题:LuoguP1491 集合位置

题目分析

此题要求的是一张图的非严格次短路。且每条边不可重复经过。

思路

采用删边法。
我们先对原先的图跑一边最短路,记录最短路的路径。
之后每次删去最短路的一条边,重新跑最短路,在这几次跑出来的结果中,取最小值,就是最终答案。

正确性证明:
显然,次短路和最短路的路径必然不是相同的。
因为最短路的路径肯定是整张图中最优的,我们每次去掉最短路上的一条路径,剩下的路径必然不会比最短路的路径更优。
在删掉一条边后的剩下的路径中取得最短路径,且肯定不会比原先图的最短路径更优,那么这一条路就必然是次短路径。

做法:
对于这道例题而言,我们先记录每个点的坐标位置,在连边时用两点间距离公式算出距离,连双向边,记得开 double
在 dijkstra 函数里传入两个参数,也就是我们要删除的边所连接的两个顶点,第一次跑最短路时传入 \(-1\) \(-1\),跑最短路是特判,如果是 \(-1\) \(-1\),那么我们每次记录上当前顶点最短路的前驱,我们可以定义数组 \(prevv\),则 \(prevv_i\) 表示编号为 \(i\) 的顶点在最短路中的前驱顶点的编号。如果传进来的参数不为 \(-1\) \(-1\),则判定当前将要松弛的边所连接的两个顶点是否为传进来的参的顶点编号,如果是的话则直接 continue,相当于把这条边给删掉了(实质上是把这条边忽视掉,和删边是一样的效果)。

剩下就是照常跑最短路的操作了。

在主函数里,我们遍历 \(prevv\) 数组中所有的边,传入 dijkstra 中,每次取 \(dist_n\) 的最小值,这样最终的最小值就是我们的答案。

\(Q\):如果这道题求的是严格次短路应该怎么办?
\(A\):在第一次求解最短路时,将最短路记录下来。之后在遍历 \(prevv\) 数组求删边后的最短路时,若求出来和第一次求解最短路的路径长度相同,则忽视掉当前结果。

代码

每次遍历 \(prevv\) 数组中所有的边,故最坏时间复杂度为 \(O(m(m+n)logn)\)。
代码实现:

#include <bits/stdc++.h>
#define int long long
const int N = 2e4 + 5;
using namespace std;
inline int read(){
	int r = 0,w = 1;
	char c = getchar();
	while (c < '0' || c > '9'){
		if (c == '-'){
			w = -1;
		}
		c = getchar();
	}
	while (c >= '0' && c <= '9'){
		r = (r << 3) + (r << 1) + (c ^ 48);
		c = getchar();
	}
	return r * w;
}
int head[N],cnt = 0;
struct edge{
	int nxt,to;
	double w;
}e[N << 1];
void edge_add(int x,int y,double z){
	cnt++;
	e[cnt].nxt = head[x];
	head[x] = cnt;
	e[cnt].to = y;
	e[cnt].w = z;
}
int n,m,prevv[N];
namespace dij{
	struct point{
		int id;
		double dis;
		friend bool operator < (point a,point b){
			return a.dis > b.dis;
		}
	};
	priority_queue<point> q;
	double dist[N];
	bool vis[N];
	void dijkstra(int dx,int dy){
		for (int i = 1;i <= n;i++){
			dist[i] = DBL_MAX;
		}
		memset(vis,0,sizeof(vis));
		dist[1] = 0;
		q.push((point){1,0});
		while (!q.empty()){
			int tmp = q.top().id;
			q.pop();
			if (!vis[tmp]){
				vis[tmp] = 1;
				for (int i = head[tmp];i;i = e[i].nxt){
					int v = e[i].to;
					double w = e[i].w;
					if (tmp == dx && v == dy || tmp == dy && v == dx){
						continue;
					}
					if (dist[v] > dist[tmp] + w){
						dist[v] = dist[tmp] + w;
						q.push((point){v,dist[tmp] + w});
						if (dx == -1 && dy == -1){
							prevv[v] = tmp;
						} 
					}
				} 
			}
		}
	}
}
using namespace dij;
signed main(){
	n = read(),m = read();
	int x[N],y[N];
	for (int i = 1;i <= n;i++){
		x[i] = read(),y[i] = read();
	}
	for (int i = 1;i <= m;i++){
		int p = read(),q = read();
		double dis = sqrt((x[p] - x[q]) * (x[p] - x[q]) + (y[p] - y[q]) * (y[p] - y[q]));
		edge_add(p,q,dis);
		edge_add(q,p,dis);
	}
	dijkstra(-1,-1);
	double ans = DBL_MAX;
	for (int i = n;i != 1;i = prevv[i]){
		int dx = i,dy = prevv[i];
		dijkstra(dx,dy);
		ans = min(ans,dist[n]);
	}
	if (ans == DBL_MAX){
		printf("-1");
		return 0;
	}
	printf("%.2lf",ans);
	return 0;
} 

二、边可重复经过的次短路问题

例题:[USACO06NOV] Roadblocks G

题目分析

此题要求的是一张图的严格次短路。且每条边可重复经过。

思路

用两个 \(dist\) 数组,分别记录最短路和次短路。
当最短路可以更新时,那么次短路就继承之前的最短路的长度。
当次短路可以更新,且次短路更新后不会超过最短路长度,那么次短路更新。

注意:

  1. 若次短路更新了,那么需要将次短路的顶点编号和 \(dis\) 也放入优先队列 \(q\) 中。因为你还不能保证目前的次短路就是最优了,还需要进行松弛更新。
  2. 相对于原来的 dijkstra 板子,我们需要去掉 \(vis\) 数组的判断,因为\(vis\) 数组肯定对最短路没有影响的,它就是用来确定目前的最短路是否是最优的。但,当一个顶点的最短路是最优的,这个顶点的次短路不一定是最优的。
  3. 相对于原来的 dijkstra 板子,我们不仅需要提前将 优先队列 \(q\) 的第一个元素的 \(id\) 取出来,我们还需要提前将第一个元素的 \(dis\) 成员取出来,因为在原来的板子里,优先队列中第一个元素的 \(dis\) 就等于 \(dist_tmp\) (\(tmp\) 为优先队列中第一个元素的 \(id\))。但,在这里我们有两个 \(dist\) 数组,当前使用哪一个 \(dist\) 数组取决于当前优先队列里第一个元素的 \(dis\) 成员。

代码

与原版 dijkstra 复杂度相同。\(O((m+n)logn)\)
\(PS\):若这道题要求非严格次短路,只需将当次短路可以更新时的后半部分条件稍作修改即可(dist[v][1]<dis_now+w 改成 dist[v][1]<=dis_now+w)。
代码实现:

#include <bits/stdc++.h>
#define int long long
const int N = 1e5 + 5;
using namespace std;
inline int read(){
	int r = 0,w = 1;
	char c = getchar();
	while (c < '0' || c > '9'){
		if (c == '-'){
			w = -1;
		}
		c = getchar();
	} 
	while (c >= '0' && c <= '9'){
		r = (r << 3) + (r << 1) + (c ^ 48);
		c = getchar();
	}
	return r * w;
}
int n,m,s;
struct edge{
	int nxt,to,w;
}e[N << 1];
int cnt = 0,head[N];
void edge_add(int x,int y,int z){
	cnt++;
	e[cnt].nxt = head[x];
	head[x] = cnt;
	e[cnt].to = y;
	e[cnt].w = z;
}
namespace dij{
	struct point{
		int id,dis;
		friend bool operator < (point a,point b){
			return a.dis > b.dis;
		}
	};
	priority_queue<point> q;
	int dist[N][5];
	void dijkstra(){
		memset(dist,0x3f,sizeof(dist));
		dist[1][1] = 0;
		q.push((point){1,0});
		while (!q.empty()){
			int tmp = q.top().id;
			int dis_now = q.top().dis;
			q.pop();
			for (int i = head[tmp];i;i = e[i].nxt){
				int v = e[i].to;
				int w = e[i].w;
				if (dist[v][1] > dis_now + w){
					dist[v][2] = dist[v][1];
					dist[v][1] = dis_now + w;
					q.push((point){v,dist[v][1]});
					q.push((point){v,dist[v][2]});
				}
				else if (dist[v][2] > dis_now + w && dist[v][1] < dis_now + w){
					dist[v][2] = dis_now + w;
					q.push((point){v,dist[v][2]});
				}
			}
		}
	}
}
using namespace dij;
signed main(){
	n = read(),m = read();
	for (int i = 1;i <= m;i++){
		int x = read(),y = read(),z = read();
		edge_add(x,y,z);
		edge_add(y,x,z);
	}
	dijkstra();
	printf("%lld",dist[n][2]);
	return 0;
}

标签:tmp,dist,int,短路,路径,单源次,算法,dis
From: https://www.cnblogs.com/WiuehPlus/p/17620315.html

相关文章

  • 关于读者阅读“改良版雪花算法”后提出的几个共性问题的回复
    你好呀,我是歪歪。周一的时候不是发了《在开源项目中看到一个改良版的雪花算法,现在它是你的了。》这篇破文章嘛。然后有好几个读者都提出了几个类似的问题,再写个续集,给大家解答一下。我就喜欢这种和读者有来有回,相互拉扯的感觉。突出一个“相互学习,共同进步。”超前消费首先......
  • 代码随想录算法训练营第十天|力扣232.用栈实现队列、力扣225.用队列实现栈
    栈与队列理论知识栈提供push和pop等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。不像是set或者map提供迭代器iterator来遍历所有元素。栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制......
  • 代码随想录算法训练营第十四天| 理论基础 递归遍历 迭代遍历
     理论基础    卡哥建议:需要了解 二叉树的种类,存储方式,遍历方式 以及二叉树的定义   文章讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html   补充的知识点:   名词的概念看卡哥文章。二叉树......
  • AcWing 850. Dijkstra求最短路 II
    题目给定一个$n$个点$m$条边的有向图,图中可能存在重边和自环,所有边权均为非负值。请你求出$1$号点到$n$号点的最短距离,如果无法从$1$号点走到$n$号点,则输出$−1$。输入格式第一行包含整数$n$和$m$。接下来$m$行每行包含三个整数$x,y,z$,表示存在一条从点$......
  • 单源最短路径算法
    单源最短路径算法1.原理单源最短路径算法是一种用于在有向图或无向图中找到从指定源节点到其他所有节点的最短路径的算法。常用的单源最短路径算法有Dijkstra算法、Floyd-Warshall算法和Bellman-Ford算法。2.Dijkstra算法Dijkstra算法是最常用的单源最短路径算法之一,它的基......
  • (未完全掌握)代码随想录算法训练营第八、九天|KMP算法;力扣28.实现strStr(),力扣459.重
    KMP算法(没掌握)主要功能:字符串匹配理论:检测文本串中是否出现过模式串前缀就是包含首字母不包含尾字母的所有子串后缀就是包含尾字母不包含首字母的所有子串最长相等前后缀:对子串分别分析,从左向右前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从......
  • Floyd 算法
    Floyd算法:动态规划中的最短路径问题一、简介Floyd算法是一种用于求解图中所有顶点对之间最短路径的动态规划算法。它是由RobertW.Floyd在1965年提出的,因此得名Floyd-Warshall算法。该算法的核心思想是使用动态规划来避免重复计算已经计算过的子问题的解。二、原理假......
  • 分解因数--递归算法
    【题目描述】给出一个正整数a,要求分解成若干个正整数的乘积,即a=a1×a2×a3×...×an,并且1<a1≤a2≤a3≤...≤an,问这样的分解的种数有多少。注意到a=a也是一种分解。【输入】第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数a(1<a<32768)。【输出】n行,每......
  • MATLAB用改进K-Means(K-均值)聚类算法数据挖掘高校学生的期末考试成绩|附代码数据
    全文链接:http://tecdat.cn/?p=30832最近我们被客户要求撰写关于K-Means(K-均值)聚类算法的研究报告,包括一些图形和统计输出。本文首先阐明了聚类算法的基本概念,介绍了几种比较典型的聚类算法,然后重点阐述了K-均值算法的基本思想,对K-均值算法的优缺点做了分析,回顾了对K-均值改进......
  • LeetCode从算法到算命—1749.任意子数组和的绝对值的最大值
    1749.任意子数组和的绝对值的最大值题目信息给你一个整数数组nums。一个子数组[numsl,numsl+1,...,numsr-1,numsr]的和的绝对值为abs(numsl+numsl+1+...+numsr-1+numsr)。请你找出nums中和的绝对值最大的任意子数组(可能为空),并返回该最大值。abs(x)......