一、BellmanFord算法
由n个点组成图,从一点v0到一点vn的最短路p,p最多由n-1条边组成。
现在有一定理: 设P={(v0,v1),(v1,v2),.....,(vn-1,vn)}为v0到vn的最短路,其中(vi,vj)表示从vi到vj的边,如果,我们能够保证按照P中的这些边的排列顺序来对每个边进行Relax操作,那么由此计算出的D[vn]即为v0到vn的最短路的长度。
bellmanFord算法,就是以上面这个定理为基础。bellmanFord通过对每条边Relax n-1次来保证上面定理的条件,进而得到v0到vn的最短路的长度。
举个例子就很清楚了,
一共有6个顶点,u到v的最短路,有5条边组成
假设组成最短路的边的标号是5,4,3,2,1<最坏情况>
6-1=5
第一次松弛:
1,2,3,4,5
取5
第二次松弛:
1,2,3,4,5
取4
第三次松弛:
1,2,3,4,5
取3
第四次松弛:
1,2,3,4,5
取2
第五次松弛
取1
构成按照5,4,3,2,1顺序松弛要求
二、问题描述
POJ 1860 :http://poj.org/problem?id=1860
<这道题也是《新编实用算法分析与程序设计竞赛》 P221页的例题,看不懂英文的,可以参见这个>
POJ 2240 :http://poj.org/problem?id=2240
题意与上面这题类似
三、问题分析
由于POJ 1860和POJ 2240两道题目基本一致,所以下面仅对POJ 1860做分析。
问题一:首先,分析为什么钱通过几次兑换之后,会变多?
注意到题目中给定的输入样例,货币2和货币3之间,货币2到货币3是1:1.1 ,但是货币3到货币2也是1:1.1,如果我们的本钱足够<减去手续费再兑换之后仍会增加>,在货币2和货币3之间兑换次数越多,最后换成货币1就越多。
问题二:构造我们的图
以每种币种为节点,币种之间的兑换关系作为边,就得到我们的图。兑换关系——货币A到货币B=(A-fee)*rate
问题三:我们的目标
通过问题一的分析和问题二的图,我们确定了输出YES的情况——图中存在正回路,由于存在正回路,导致钱可以无限增长,总可以在正回路循环i次之后,再兑换成最初的货币后发现,钱变多了。所以,我们的程序的目标就是寻找图中是否存在正回路。
问题四:正回路和bellmanFord算法
bellmanFord算法强大之处在于,它允许边权值为负,而且可以检测出负圈。所以,我们正好利用这个来检测正回路,通过修改Relax的条件,就可以把单源最短路转换成求解最长路径。如果,每条边经过n-1次Relax操作,仍存在可以修改的D值的点,那么就可以说存在正回路了!
四、程序实现
1 #include<iostream> 2 #include<fstream> 3 using namespace std; 4 5 struct edge{ 6 int u,v; 7 double rate,fee; 8 } edges[404]; 9 10 int n = 0; 11 int f = 0; 12 double sum = 0; //f最初持有的币种,sum表示初始钱数 13 double value[202]; 14 int all; 15 16 int Relax(int j) 17 { 18 double now = (value[(edges[j].u)]-edges[j].fee)*edges[j].rate; 19 if(value[(edges[j].v)] < now) 20 { 21 value[(edges[j].v)] = now; 22 return 1; 23 } 24 return 0; 25 } 26 27 int bellmanFord() 28 { 29 for(int i = 0;i < n-1;i++) 30 { 31 int flag = 0; 32 for(int j = 0;j < all;j++) 33 if(Relax(j)) 34 flag = 1; 35 if(!flag) break;//这一次bellmanFord没有更新任何点 36 } 37 38 for(int i = 0;i < all;i++) 39 { 40 //存在能无限增大的正回路 41 if(value[edges[i].v] < ((value[edges[i].u]-edges[i].fee)*edges[i].rate)) 42 { 43 return 1; 44 } 45 } 46 return 0; 47 } 48 49 int main() 50 { 51 int m = 0; 52 while(cin >> n >> m >> f >> sum) 53 { 54 all = 0; 55 for(int j = 0;j < m;j++) 56 { 57 cin >> edges[all].u>>edges[all].v>>edges[all].rate>>edges[all].fee; 58 all++; 59 edges[all].v = edges[all-1].u; 60 edges[all].u = edges[all-1].v; 61 cin>>edges[all].rate>>edges[all].fee; 62 all++; 63 } 64 for(int k = 1;k < n+1;k++) 65 value[k] = 0; 66 value[f] = sum; 67 if(bellmanFord()){ cout <<"YES"<<endl;} 68 else {cout <<"NO"<< endl;} 69 } 70 return 0; 71 }
五、注意
POJ 2240这题基本和1860这题一样,唯一麻烦的地方可能就是数据处理上,这里可以采用map,以string为key,以int为value
1 #include <map> 2 3 MAP[str] = i;
六、感想
自己通过自己独立分析问题,自己独立编程实现,最后这个题目AC,这个节奏还是很好的~
最后,YZY,我想你!~
浙公网安备 33010602011771号