第三章 搜索与图论(二)——最短路问题

这篇具有很好参考价值的文章主要介绍了第三章 搜索与图论(二)——最短路问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


源点表示起点,汇点表示终点
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论
一些认识:
m和 n 2 n^2 n2一个级别是稠密图,m和n一个级别是稀疏图
最短路问题不区分有向图与无向图,因为无向图是一种特殊的有向图,能解决有向图的最短路问题,就能解决无向图的最短路问题

单源最短路

起点确定,终点是除起点外的其他点
默认n表示点数,m表示边数

  1. 所有边权为正数
  • 朴素Dijkstra O( n 2 n^2 n2),和边数无关,用于稠密图
  • 堆优化版Dijkstra O(mlogn) m = n 2 n^2 n2,用于稀疏图
  1. 边权存在负数
  • Bellman-Ford O(nm)
  • SPFA O(m),最坏O(nm),是Bellman-Ford的优化

朴素Dijkstra

基于贪心思想,每次选择距离源点最近的点

维护一个s集合,存储已经确定最短路径的点
一开始该集合中只有源点,不断地将点加入到s集合中,直到图中所有点都属于s集合,最短路求解结束

如何将点加入s集合?

  1. 遍历图中不属于s的点,选择其中与源点距离最小的点加入s
  2. 对于此时不属于s的点,用新加入s的点尝试更新其他点的最短距离

重复以上两个步骤,直到图中所有点都属于s
如何用代码实现?
dis数组存储每个点与源点的最短距离,初始化:源点到源点的距离为0
g数组为邻接矩阵,存储图中每条边的权值(若是稀疏图则使用邻接表
vt数组用来维护s集合,存储该点是否在s中的bool值

  1. 初始化dis[源点的编号] = 0, dis[其他点] = +∞
  2. 遍历dis数组不属于s的点,选择其中与源点距离最短的点加入s
  3. 根据新加入s的点,更新dis数组中其他不属于s的点,假设新加入点的编号为x,`dis[y] = min(dis[y], dis[x] + g[x][y])
  4. 重复2,3两个步骤,直到所有点属于s

第三步中,对于已经确定最短路径的点(属于s中的点) ,需要根据该点更新其他不属于s中的点。此时进行操作dis[y] = min(dis[y], dis[x] + g[x][y]),其实不需要特别要求该点不属于s,因为属于s的点已经确定了最短距离,不论是否进行min操作都不影响已经确定的最短距离。所以在min操作之前判断该点是否属于s的操作是无关紧要的

模板:

bool vt[N];
int dis[N], g[x][y];

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    
    for (int i = 0; i < n; ++ i )
    {
        int x = -1;
        for (int j = 1; j <= n; ++ j)
            if (!vt[j] && (x == -1 || dis[j] < dis[x])) 
                x = j;

        vt[x] = true; // 从不属于s的点中选择距离源点最近的点
        for (int y = 1; y <= n; ++ y) // 用新加入的点更新其他点
            dis[y] = min(dis[y], dis[x] + g[x][y]);
    }
}

外循环迭代n次,每次选择与源点距离最近的点放入集合中,集合中的点为已经确定最短距离的点。迭代n次后,图中所有点都进入了s集合,即确定了所有点的最短距离
每一次迭代要做的事:

  1. 在不属于s中的点中,找到与源点距离最近的点
  2. 用该点更新其他(不属于s的)点

堆优化版Dijkstra

用堆优化朴素Dijkstra算法,时间复杂度可以达到O(mlogn)
若手写一个支持修改任意位置的堆,空间复杂度为O(n)
若使用优先队列,空间复杂度将达到O(m),存储稠密图比较浪费空间

朴素Dijkstra算法中,在不属于s的点中,找距离源点最近的点,时间复杂度为O(n)
外循环需要迭代n次,所以总得时间复杂度为O( n 2 n^2 n2),这是朴素Dijkstra算法的效率瓶颈
用堆结构对朴素算法进行优化,我们能以O(1) 的时间取出距离源点最近的点,不过代价就是提高了空间复杂度,从O(n) 提高到了O(m)。以及每次维护堆时,时间复杂度为O(logn)

同时,若使用邻接矩阵存储边,用新加入的点更新其他点时,时间复杂度为O(n),总的时间复杂度为O( n 2 n^2 n2)。也是一个瓶颈,不过很好解决,用邻接表存储边,总的时间复杂度为O(m)

而使用邻接表存储,不用考虑重边问题。邻接矩阵只能存储两点间的一条边,所以要选择重边中最小的边

代码如何实现?
用优先队列存储pairfirst为点到源点的距离,second为点的编号
由于优先队列无法修改元素,所以图中的每条边都会进入一次优先队列,此时存在冗余且无效的数据
具体情况是:队列中存在second相同的pair,此时first较大的pair为无效数据。但我们无法删除任意位置的pair,只能删除堆顶的pair。所以取出堆顶元素时需要判断该pairsecond是否已经在s中(最短距离是否已经确定
若已经在s中,说明不需要进行接下来的操作(加入s和用该点更新其他点),所以此时直接continue

模板:

typedef pair<int, int> PII;

int h[N], e[N], ne[N], w[N], idx = 1;
priority_queue<PII, vector<PII>, greater<PII>> q;
int dis[N];
bool vt[N];

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    q.push({ 0, 1 }), dis[1] = 0;
    
    while (q.size())
    {
        auto t = q.top();
        q.pop();
        
        int x = t.second, d = t.first;
        if (vt[x]) continue;
        vt[x] = true;
        
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (!vt[y] && d + w[i] < dis[y])
            {
                dis[y] = d + w[i];
                q.push({ dis[y], y });
            }
        }
    }
}

Bellman Ford算法

暴力美学

用于处理负权边
存储边的结构没有要求,可以用简单的结构体存储

struct Edge
{
	int x, y, w;
}edges[M];

循环n次,每次都遍历所有边
遍历某条边时,对其进行松弛操作:dis[y] = min(dis[y], dis[x] + w[i]),其中x是当前边的起点,y是当前边的终点,w[i]是当前边的权重
每次的松弛操作其实是在尝试更新dis[y],而更新依赖于dis[x]。若dis[x]不是无穷大,而是被其他松弛操作更新了,那么dis[y]也可以被当前松弛操作更新

外循环与dis数组的关系:若循环进行了k次,dis数组存储从源点开始走,不超过k条边,递达其他点的最短距离

若存在负环,则最短路不一定存在(负环与源点和目标点不连通时则最短路存在
用Bellman-Ford判断负环:
第n次外循环时,若更新了dis数组,说明某条最短路中有n条边,即n+1个点,根据抽屉原理,该图存在负环
但Bellman-Ford的时间复杂度较高,一般用SPFA解决负环问题

三角不等式:dis[y] <= dis[x] + w

串联问题:每次对所有边进行松弛操作时,应该基于上一次对所有边进行松弛操作后的状态。什么意思呢?对所有边进行了一次松弛操作后,我们要备份此时的dis数组,作为上一次的状态保存
若不备份dis数组,而直接进行松弛操作。修改dis数组的某些数据后,此时的dis数组不再是上一次对所有边进行松弛操作后的状态,基于这个状态进行松弛操作得到的结果多半是错误的

串联问题与dp状态压缩导致的问题类似

模板:

struct Edge
{
	int x, y, w;
}edges[M];

int dis[N], bup[N];
void bellman_ford()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    for (int i = 0; i < k; ++ i )
    {
        memcpy(bup, dis, sizeof(dis));
        for (int j = 0; j < m; ++ j )
        {
            auto t = edges[j];
            dis[t.y] = min(dis[t.y], bup[t.x] + t.w);
        }
    }
}

SPFA

SPFA是对Bellman-Ford算法的一个优化
注意松弛操作:dis[y] = min(dis[y], dis[x] + w[i]) 什么时候这个操作才是有效的?只有当dis[x]变小,dis[y]才可能变小 当dis[x]不变时,dis[y]`一定不会变换,所以Bellman-Ford中,大部分的更新多余且无效

如何使得每次的更新都是有效的呢?
运用广搜的思想,将每次更新(有效松弛)的点进入队列
初始时,将源点入队,表示源点的最短路被更新
每次取出队头的点,对连接该点的所有边进行松弛操作。通过邻接表将与该点邻接的点入队,因为这些点的dis距离被更新了
直到队列为空,此时整张图更新完成

注意,为防止一个点的重复地进入队列,要维护每个点的状态:某个点入队之前,先判断该点是否已经在队列中

模板:

// N为点数,M为边数
int dis[N];
int h[N], e[M], ne[M], w[M], idx = 1;
int q[N], hh, tt = -1;
bool st[N];

void add(int x, int y, int d)
{
	e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void spfa()
{
	memset(dis, 0x3f, sizeof(dis));
	q[++ tt] = 1, st[1] = true, dis[1] = 0; // 假设1号点为源点
	while (tt >= hh)
	{
		int x = q[hh ++ ];
		st[x] = false;
		for (int i = h[x]; i != -1; i = ne[i] )
		{
			int y = e[i];
			if (dis[x] + w[i] < dis[y]) 
			{
				dis[y] = dis[x] + w[i];
				if (!st[y]) st[y] = true, q[++ tt ] = y;
			}
		}
	}
}

出题者可能故意卡SPFA的时间,使时间复杂度达到最坏的情况O(nm)
此时只能使用其他算法

SPFA求负环

和Bellman-Ford一样,SPFA也是运用抽屉原理判断图中是否有负环

在SPFA求负环的算法中,虽然求解过程与原SPFA差不多,但是有些思想是完全不一样的
比如dis数组不再表示其他点到源点的距离,可以认为该数组没有任何含义
初始化时,将dis数组的每个值置为0,甚至任意数都是可以的,只要保证每个位置的值一样
由于现在要判断图中是否存在负环,若从一个点出发找负环,在非连通图中可能无法实现,所以不能用单源点判断负环。所以初始化时,将所有的点入队,表示每个点都是源点,此时就算是非连通图也能找到负环
dis[x] + w[i] < dis[y]:什么时候会执行该操作?当dis所有值都相同时,只有w[i]为负数(存在负权边)时,该操作才会执行。此时维护cnt数组,cnt[y] = cnt[x] + 1表示遍历了某条存在负权边的路径中的一条边,当cnt数组中的某个值大于等于n,即cnt[i] >= n时,根据抽屉原理,该图中存在负环

所以,求解负环时,SPFA不再是单源求解过程,而是多源求解过程。求解的问题也不再是最短路,而是负环的判断
此时,dis数组的含义发生变化,同时引入cnt数组,存储遍历某条存在负权边的路径的边的次数

非连通图中的负环,边数不可能大于等于n吧。那么根据cnt[i] >= n判断图中是否存在负环有问题吗?cnt数组存储在某条存在负权边的路径中遍历的次数,当一个负环的边数小于n,那么我们将一直遍历这个负环,直到cnt[i] >= n停止
最坏的情况时,所有的点共同构成了负环,负环中点的数量与边的数量都等于n,此时我们将遍历图中所有点一次,接着cnt[i] == n,遍历结束,说明图中存在负环

模板:

int h[N], e[M], ne[M], w[M], idx = 1;
int q[N], hh, tt = -1;
int dis[N], cnt[n];
bool st[N];

void add(int x, int y, int d)
{
	e[idx] = y, ne[idx] == h[x], w[idx] = d, h[x] = idx ++ ;
}

bool spfa()
{
	for (int i = 1; i <= n; ++ i )
		q[++ tt] = i, st[i] = true;

	while (tt >= hh)
	{
		int x = q[hh ++];
		st[x] = false;
		for (int i = h[x]; i != -1; i = ne[i])
		{
			int y = e[i];
			if (dis[x] + w[i] < dis[y])
			{
				dis[y] = dis[x] + w[i];
				cnt[y] = cnt[x] + 1;
				if (cnt[y] >= n) return true;
				if (!st[y]) st[y] = true, q[++ tt] = y;
			}
		}
	}
	return false;
}

多源汇最短路

Floyd

任意起点与终点的最短路问题
Floyd的时间复杂度为: O( n 3 n^3 n3)

d[k][i][j]:从i号点出发,只经过1~k号这些中间点,到达j号点的最短距离
状态更新:d[k][i][j] = d[k - 1][i][k] + d[k - 1][k][j],观察表达式,发现第一个维度可以压缩,即d[i][j] = d[i][k] + d[k][j]

过程很简单,三重循环进行动态规划
模板:

void Floyd()
{
	for (int k = 1; k <= n; ++ k )
		for (int i = 1; i <= n; ++ i )
			for (int j = 1; j <= n; ++ j )
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

注意d数组要初始化:初始化的值与邻接矩阵一样,由于要求解的问题是多源汇最短路,所以要初始化源点到源点的距离为0


最短路练习题

849. Dijkstra求最短路 I

849. Dijkstra求最短路 I - AcWing题库
对图中节点进行编号,题目要求1号点到n号点的最短距离
以1号点作为源点,用朴素Dijkstra更新dis数组,获取单源最短路

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 1e5 + 10;
int dis[N], g[N][N];
bool vt[N];
int n, m;

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    
    for (int i = 0; i < n; ++ i )
    {
        int x = -1;
        for (int j = 1; j <= n; ++ j)
            if (!vt[j] && (x == -1 || dis[j] < dis[x])) 
                x = j;

        vt[x] = true; // 从不属于s的点中选择距离源点最近的点
        for (int y = 1; y <= n; ++ y) // 用新加入的点更新其他点
            dis[y] = min(dis[y], dis[x] + g[x][y]);
    }
}

int main()
{
    memset(g, 0x3f, sizeof(g));
    
    scanf("%d%d", &n, &m);
    int x, y, d;
    while (m -- )
    {
        scanf("%d%d%d", &x, &y, &d);
        g[x][y] = min(g[x][y], d);
    }
    
    dijkstra();
    
    if (dis[n] == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", dis[n]);
    
    return 0;
}

850. Dijkstra求最短路 II

850. Dijkstra求最短路 II - AcWing题库
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论

题目给定的图为稀疏图,使用邻接表存储
和第一题一样,要求1号点到n号点的最短距离,以1号点为源点,用Dijkstra求得单源最短路

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII>> q;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], w[N], idx = 1;
int dis[N];
bool vt[N];
int n, m;

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    q.push({ 0, 1 }), dis[1] = 0;
    
    while (q.size())
    {
        auto t = q.top();
        q.pop();
        
        int x = t.second, d = t.first;
        if (vt[x]) continue;
        vt[x] = true;
        
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (!vt[y] && d + w[i] < dis[y])
            {
                dis[y] = d + w[i];
                q.push({ dis[y], y });
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y, d;
    while (m -- )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d);
    }
    
    dijkstra();
    if (dis[n] == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", dis[n]);
    
    return 0;
}

debug:优先队列需要建小堆,默认是建立大堆,这个真的没有注意到
queue<PII, vector<PII>, greater<PII>>:传greater建立小堆


853. 有边数限制的最短路

853. 有边数限制的最短路 - AcWing题库
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论

求解具有负权边的最短路问题一般使用SPFA,因为其时间复杂度较低。但有些最短路问题只能用Bellman-Ford求解,因为这类题有边数的限制
限制的边数就是外循环的次数,内循环还是要对所有边进行松弛操作
由于图中可能存在负权边,当源点无法递达某一个点时,该点的dis距离可能不是最大值,而是小于最大值的较大值
假设源点无法到达目标点y点,但x点能到达y点,此时源点也无法递达x点,即dis[x] = +∞。而连接x与y的边权为负数,那么目标点y的dis距离将被更新:dis[y] = dis[x] + w,其中的dis[x]为正无穷,w为负数,那么dis[y]将被更新成一个小于正无穷的较大数
所以判断源点是否能递达其他点时,不能判断dis[i] == 0x3f3f3f3f,而应该判断dis[i] > 0x3f3f3f3f / 2

以下代码存在问题,无法AC

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 10010;
int dis[N], bup[N];
int n, m, k;

struct Edge
{
    int x, y ,w;
}edges[M];

int Bellman_Ford()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    for (int i = 0; i < k; ++ i )
    {
        memcpy(bup, dis, sizeof(dis));
        for (int j = 0; j < m; ++ j )
        {
            auto e = edges[j];
            dis[e.y] = min(dis[e.y], bup[e.x] + e.w);
        }
    }
    
    if (dis[n] > 0x3f3f3f3f / 2) return -1;
    return dis[n];
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    int x, y, w;
    for (int i = 0; i < m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &w);
        edges[i] = { x, y, w };
    }
    
    int t = Bellman_Ford();
    if (t == -1) puts("impossible");
    else printf("%d\n", t);
    
    return 0;
}

debug:某些最短路可能是负值,当最短路为-1时,Bellman_Ford()返回的t也是-1,此时-1不能作为区分是否存在最短路的值
所以应该直接在main函数中判断dis[n]0x3f3f3f3f的关系

以下是ac代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 10010;

struct Edge
{
    int x, y, w;
}edges[M];

int dis[N], bup[N];
int n, m, k;

void bellman_ford()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    for (int i = 0; i < k; ++ i )
    {
        memcpy(bup, dis, sizeof(dis));
        for (int j = 0; j < m; ++ j )
        {
            auto t = edges[j];
            dis[t.y] = min(dis[t.y], bup[t.x] + t.w);
        }
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    int x, y, d;
    for (int i = 0; i < m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        edges[i] = { x, y, d };
    }
    
    bellman_ford();
    if (dis[n] > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n", dis[n]);
    
    return 0;
}

851. spfa求最短路

851. spfa求最短路 - AcWing题库
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论

debug:以下spfa是错误的,错在松弛操作的判断条件,当满足dis[x] + w[i] < dis[y]时,就要更新dis[y] = dis[x] + w[i]。然后再判断y是否在队列中,不能将y是否在队列作为松弛操作的判断条件

void spfa()
{
    memset(dis, 0x3f, sizeof(dis));
    q[++ tt] = 1, dis[1] = 0, st[1] = true;
    while (tt >= hh)
    {
        int x = q[hh ++ ];
        st[x] = false;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (!st[y] && dis[x] + w[i] < dis[y])
                st[y] = true, dis[y] = dis[x] + w[i], q[++ tt] = y;
        }
    }
}

以下是ac代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;

int h[N], e[N], ne[N], w[N], idx = 1;
int q[N], hh, tt = -1;
int dis[N];
bool st[N]; // 某个点是否在队列中
int n, m;


void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void spfa()
{
	memset(dis, 0x3f, sizeof(dis));
	q[++ tt] = 1, st[1] = true, dis[1] = 0; // 假设1号点为源点
	while (tt >= hh)
	{
		int x = q[hh ++ ];
		st[x] = false;
		for (int i = h[x]; i != -1; i = ne[i] )
		{
			int y = e[i];
			if (dis[x] + w[i] < dis[y]) 
			{
				dis[y] = dis[x] + w[i];
				if (!st[y]) st[y] = true, q[++ tt ] = y;
			}
		}
	}
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y, d;
    while (m -- )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d);
    }
    
    spfa();
    
    if (dis[n] == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", dis[n]);
    return 0;
}

852. spfa判断负环

852. spfa判断负环 - AcWing题库
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论

#include <iostream>
#include <cstring>
using namespace std;

const int N = 2010, M = 10010;
int h[N], e[M], ne[M], w[M], idx = 1;
int q[M], hh, tt = -1;
int dis[N], cnt[N];
bool st[N];
int n, m;

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

bool spfa()
{
    for (int i = 1; i <= n; ++ i )
        q[++ tt] = i, st[i] = true;
    
    while (tt >= hh)
    {
        int x = q[hh ++ ];
        st[x] = false;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (dis[x] + w[i] < dis[y])
            {
                dis[y] = dis[x] + w[i];
                cnt[y] = cnt[x] + 1;
                if (cnt[y] >= n) return true;
                if (!st[y]) q[++ tt] = y, st[y] = true;
            }
        }
    }
    return false; 
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y, d;
    while (m -- )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d);
    }
    
    if (spfa()) puts("Yes");
    else puts("No");
    
    return 0;
}

debug:用原生数组模拟队列时,队列的长度要设置为M,之前设置为int q[N],导致了TLE,排查了很久的逻辑错误,结果是这里错了。看来这种边界问题需要提前想好啊,懒得想就用STL的,但是时间差距嘛,看下图
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论


854. Floyd求最短路

854. Floyd求最短路 - AcWing题库
第三章 搜索与图论(二)——最短路问题,AcWing算法课 课程记录,图论文章来源地址https://www.toymoban.com/news/detail-533917.html

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 210, M = 20010;
int n, m, k;
int d[N][N];

void flody()
{
    for (int k = 1; k <= n; ++ k )
        for (int i = 1; i <= n; ++ i )
            for (int j = 1; j <= n; ++ j )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
    memset(d, 0x3f, sizeof(d));
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; ++ i ) d[i][i] = 0;
    
    int x, y, w;
    while (m -- )
    {
        scanf("%d%d%d", &x, &y, &w);
        d[x][y] = min(d[x][y], w);
    }
    
    flody();
    
    while (k -- )
    {
        scanf("%d%d", &x, &y);
        int t = d[x][y];
        if (t > 0x3f3f3f3f / 2) puts("impossible");
        else printf("%d\n", t);
    }
    return 0;
}

到了这里,关于第三章 搜索与图论(二)——最短路问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第三章 图论 No.2单源最短路之虚拟源点,状压最短路与最短路次短路条数

    dp是特殊的最短路,是无环图(拓扑图)上的最短路问题 1137. 选择最佳线路 1137. 选择最佳线路 - AcWing题库 对于每组测试数据,该重置的数据要重置,我没有重置idx,导致TLE 处理反向建图,还有一种扩展做法:虚拟源点 设置虚拟源点,与每个起点之间连接边权为0的边 原问题

    2024年02月14日
    浏览(49)
  • 第三章 图论 No.3 flody之多源汇最短路,传递闭包,最小环与倍增

    flody的四个应用: 多源汇最短路 传递闭包 找最小环 恰好经过k条边的最短路 倍增 多源汇最短路:1125. 牛的旅行 1125. 牛的旅行 - AcWing题库 直径概念:同一连通块中,两个距离最远的点之间的距离 如何求直径?由于图中存在着两个连通块,所以直接对全图做一个flody,就能更

    2024年02月14日
    浏览(45)
  • 搜索与图论第六期 最短路问题

    Dijkstra算法是一种著名的图算法,主要用于求解有权图中的单源最短路径问题。它由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger Wybe Dijkstra)在1956年首次提出。Dijkstra算法的核心思想是通过以下步骤逐步构建最短路径树: 初始化:创建一个空白的最短路径字典,其中每

    2024年02月20日
    浏览(42)
  • 搜索与图论(acwing算法基础)

    排列数字 n皇后 走迷宫 单链表 点击跳转至例题 idx存的是指针 树与图的深度优先搜索 树的重心 每个节点都是一个单链表 模拟队列 hh = 0 , tt = -1 有向图的拓扑序列 都是从前指向后,即有向无环图(不能有环) 所有入度为0的点,都能排在前面的位置 删掉t-j的边,仅仅是j的入度

    2024年02月08日
    浏览(45)
  • 第3章:搜索与图论【AcWing】

    阅读前导 本文默认读者有数据结构和图论基础,本文是对图论的几个代表性算法的入门,虽然题目的解法比较朴素,但是比较好理解。 首先简单复习一下离散数学中图论的相关概念。 图是由顶点和边组成,顶点一般表示对象,边一般表示对象之间的关系。 在图论中,多个顶

    2024年02月03日
    浏览(51)
  • Acwing-基础算法课笔记之搜索与图论

    bellman-ford算法适用于负权边的图,求 1 到 n 的最多经过k条边的最短距离。 如图所示: 1 2 3 dist 0 ∞ infty ∞ ∞ infty ∞ ⇓ Downarrow ⇓ 1 2 3 dist 0 1 ∞ infty ∞ ⇓ Downarrow ⇓ 1 2 3 dist 0 1 2 此过程中出现了串联的结果,所以是错误的,此时需要进行备份操作。 备份操作如下: 为了

    2024年01月20日
    浏览(55)
  • acwing算法基础课(第三讲 搜索与图论)

    void dfs(int u){ if(n == u){ for(int i = 0;i n;i++) puts(g[i]); puts(“”); return; } for(int i = 0;i n;i++){ if(!col[i] !dg[u+i] !udg[n - u + i]){ g[u][i] = ‘Q’; col[i] = dg[u+i] = udg[n - u + i] = true; dfs(u+1); col[i] = dg[u+i] = udg[n - u + i] = false; g[u][i] = ‘.’; } } } int main(){ scanf(“%d”,n); for(int i = 0;i n;i++){ for(int j = 0;j

    2024年04月10日
    浏览(54)
  • acwing算法基础之搜索与图论--kruskal算法

    kruskal算法的关键步骤为: 将所有边按照权重从小到大排序。 定义集合S,表示生成树。 枚举每条边(a,b,c),起点a,终点b,边长c。如果结点a和结点b不连通(用并查集来维护),则将这条边加入到集合S中。 kruskal算法的时间复杂度为O(mlogm),它用来解决稀疏图的最小生成树问题

    2024年02月05日
    浏览(44)
  • ✔ ★ 算法基础笔记(Acwing)(三)—— 搜索与图论(17道题)【java版本】

    1. 排列数字(3分钟) 每次遍历dfs参数是 遍历的坑位 原题链接 2. n-皇后问题 原题链接 方法 1. 按行遍历(过程中有回溯、剪枝) 思想: 每次递归中,遍历一行的元素,如果可以放皇后,就递归到下一行,下一行中不行了,就返回来,回溯, 方法2. 按每个元素遍历(没有减枝)

    2024年02月05日
    浏览(48)
  • 第三章 图论 No.13拓扑排序

    拓扑序和DAG有向无环图联系在一起,通常用于最短/长路的线性求解 裸题:1191. 家谱树 1191. 家谱树 - AcWing题库 差分约束+拓扑排序:1192. 奖金 1192. 奖金 - AcWing题库 由于图中所有边权都是正数,可以直接使用topsort求解差分约束问题 根据题意,要求一个最小值,使用最长路求解

    2024年02月12日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包