备战蓝桥杯—有边数限制的最短路 (bellman_ford+)——[AcWing]有边数限制的最短路

因为近期在学图,所以顺带的写一篇最短路的备战蓝桥杯文章。

最短路(单源)

所有边权都为正数有两种算法:

1.朴素Dijkstra    O(n^2)

2.堆优化的Dijkstra    O(mlogn)

存在负权边有两种算法:

1.Bellman-Ford    O(nm)

2.SPFA    一般O(m), 最坏 O(nm)

今天,我来介绍一下Bellman-Ford(存在负权+有边数限制)

存在负权且有边数限制——》Bellman-Ford(在我所学算法中 只能用个算法

首先

我们先复习一下Bellman-Ford 算法

简单来讲Bellman-Ford 算法核心代码只有4行(双重for循环)

for(k = 1; k <= n - 1; k++)
for(i = 1; i <= m; i++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
  1. 外循环:n 表示顶点的个数,循环 n - 1次(原因后面会说)
  2. 内循环:m 表示边的个数,循环 m次
  3. dis数组的作用与 Dijkstra算法中是一样的
  4. v u w三个数组表示一个边的信息:起点、终点、权值

特别的(要注意的):

relax(松弛)操作

if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];

最后附上 Bellman-Ford  的板子:

// 算法核心语句
for (k = 1; k <= n - 1; k++) {
   for (i = 1; i <= m; i++) {
       if (dis[v[i]] > dis[u[i]] + w[i])
           dis[v[i]] = dis[u[i]] + w[i];
        //或直接写成
        //dis[v[i]] = min(dis[v[i]],dis[u[i]] + w[i]);
   }
}

//下面代码是用来检测负权回路的
// 若题需要检查负权回路则加上,若不需要则无需写!!!
int flag = 0;
for(i = 1; i <= m; i++)
   if(dis[v[i]] > dis[u[i]] + w[i]) 
       flag = 1;
if(flag == 1)
   printf("此图是负权回路");

话不多说,还是以例题讲解(滑稽保命

题目如下:

给定一个 n 个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出从 1号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n号点,输出 impossible。注意:图中可能 存在负权回路

输入格式

第一行包含三个整数 n,m,k。接下来 m行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示从 1号点到 n 号点的最多经过 k条边的最短距离。如果不存在满足条件的路径,则输出 impossible

数据范围

1≤n,k≤500,1≤m≤10000,任意边长的绝对值不超过 10000。

输入样例:

3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3

AC代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e4+9; 
struct nn
{
	int x,y,z;
}s[N];
int dist[N],backup[N];
int n,m,k;
int bellman_ford()
{
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
	for(int i = 1;i<=k;i++)
	{
		memcpy(backup,dist,sizeof dist);
		for(int j = 1;j<=m;j++)
		{
			int x = s[j].x, y = s[j].y, z = s[j].z;
			dist[y] = min(dist[y],backup[x]+z);
		}
	}
	return dist[n];  
}
int main()
{
	scanf("%d %d %d",&n,&m,&k);
	for(int i = 1;i<=m;i++)
	{
		scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].z); 
	}
	int ans = bellman_ford();
	if(ans>0x3f3f3f3f/2)
	puts("impossible");
	else
	printf("%d\n",ans);
}

代码解析(题解):

这个题就是典型的 有边数限制的最短路(且有负权边)问题

那我们就直接套Bellman-Ford  的板子就可以了

不过我们需要注意一点——》避免  串联

如何去实现呢?

首先我们要开一个备份数组backup

最后在Bellman-Ford的基础上进行修改:

for(int i = 1;i<=k;i++)
	{
		memcpy(backup,dist,sizeof dist);
		for(int j = 1;j<=m;j++)
		{
			int x = s[j].x, y = s[j].y, z = s[j].z;
			dist[y] = min(dist[y],backup[x]+z);
		}
	}

每次relax(松弛)都用上一次的结果进行(这样就很好的避免串联了)

如何做到有边数限制的最短路呢?

不难发现,用外层for控制

注:memcpy的效率很高

最后,感谢您的阅读!!!

 每一发奋努力的背后,必有加倍的赏赐。

猜你喜欢

转载自blog.csdn.net/m0_60593173/article/details/122386033