bellman_ford算法
Bellman-Ford算法,对于一个有向图,可以分别求出图中所有点到一个确定点的最短距离。
基本思想就是枚举每一个点,判断通过该边能否使得其起点到原点的距离变短。
对于边3-2,它可以使3-1变成3-2-1,从而使其距离变短,此过程称为松弛。(松弛点数,拉紧距离)
边3-2可以松弛的条件:
1.边3-2存在。(对于核心代码来说,枚举所有边就已经保证了其存在,否则将不可能被枚举出来)
2.边2-1存在。(对于核心代码,实际实现的时候,2-1的距离设为的为Inf,即一个很大的数。)
3.边3-2权值+边2-1权值<边3-1权值。(判断语句实现)
题目
给定一个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≤5001≤n,k≤500,
1≤m≤100001≤m≤10000,
任意边长的绝对值不超过10000。
代码:
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=505,M=100010; int n,m,k; int dist[N],backup[N];//dist存储初始节点到目标节点的距离,backup用于储存上一次的最短距离 struct ford//定义一个结构体 { int a,b,w; }ford[M]; int bellman_ford() { memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=1;i<=k;i++) { memcpy(backup,dist,sizeof dist);//copy一遍距离 for(int j=1;j<=m;j++) { int a=ford[j].a,b=ford[j].b,w=ford[j].w; dist[b]=min(dist[b],backup[a]+w);//松弛操作 } } if(dist[n]>=0x3f3f3f3f/2) return 0;//由于你可能是在0x3f3f3f3f的基础上进行松弛操作,因此距离可能是小于0x3f3f3f3f的 else return dist[n]; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); ford[i]={a,b,w}; } int ans=bellman_ford(); if(ans) printf("%d\n",ans); else puts("impossible"); return 0; }
进行松弛操作的意义就是:避免你在限定了只能走n步的情况下走了n+m步。
原因:
3 3 1
1 2 1
2 3 1
1 3 3
这个样例,限定了你只能走1步,这样当你在第一步进行松弛操作时,如果你没有copy则你会将使用1->2松弛之后的距离1来进行下一步松弛,即使1->3的距离变成了1->2+2->3,这样就相当于你走了2步,而题目要求只能走1步;如果你进行了copy之后,你会将使用copy之后的无穷大来进行松弛2->3操作这时就不是最小值,也就是答案输出的是1->3松弛的3,这样就避免了你多走了1步。
推广到一般情况就是这个copy应该是避免你多走了几步,因为当你走到某一个点,并且更新了最小值时,copy一遍之后就是在上一次更大距离的情况下更新,这样得到的距离并不是最小的,这样当你下一次增加了一步之后就可以当前更小的距离来更新,否则你就会直接在当前最小的距离上更新,相当于多走了1步。