一、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 }
View Code

          五、注意

                POJ 2240这题基本和1860这题一样,唯一麻烦的地方可能就是数据处理上,这里可以采用map,以string为key,以int为value

1 #include <map>
2 
3 MAP[str] = i;
View Code

 

          六、感想

                自己通过自己独立分析问题,自己独立编程实现,最后这个题目AC,这个节奏还是很好的~

                最后,YZY,我想你!~ 

posted on 2014-03-19 21:35  Oloo  阅读(148)  评论(0)    收藏  举报