【初识——最大流】 hdu 1532 Drainage Ditches(最大流) USACO 93

最大流首次体验感受——

 

什么是最大流呢?

从一个出发点(源点),走到一个目标点(汇点),途中可以经过若干条路,每条路有一个权值,表示这条路可以通过的最大流量。

最大流就是从源点到汇点,可以通过的最大流量。

接下来我们看一个图——

图1

这个图中,s是源点,t是汇点。期间可以经过2, 3, 4, 5, 6几个点。每条边上有两个权值,其中第一个表示当前通过这条边的流量,第二个表示这条边最大可以通过的流量。

最佳情况,即最大可以通过的流量的一种情况是这样的——

图2

但还有一种情况,同样可以达到最大流——

图3

这里得到两个结论:

1. 每条路径中都有至少一条边是满的。

2. 最大流可能不止一种情况。

 

 

接下来我们再看另一个图:

图4

如果在这个图上找最大流该怎么找?

这样?

图5

不对,这个图乍一看好像满足每条路径上都有一个满流的边这个条件,但是其实还有更大的流——

图6

怎么办呢?

我们可以通过这个方式从图5变到图6——

图7

这里我们的可以这样理解,在我们走出图5 的结果以后,我们允许图中出现图7中的绿色的边,然后我们就得到了绿色的数字所标示出的一条新路,通过这条路径,我们就获得了最大流。

如果我们获得了一个流量图,这个流量图中每条路径上都有一条边是满流了,如何判断这是不是一个最大流的图呢?通过上面的方法,我们在通过某条边之后,在这两个点之间构造一条反向的并且和通过的流量大小相同的边(称为反向边)。这样,就可能产生一条新路,使整个图中的流量增加。那么,我们不断地构造这种边,直到无法寻找到新的路径为止(称为增广路径),是不是就得到了最大流呢?

总结起来,每次找到一条增广路,增广路中每条边的值,都减去路径中,边值最小的边的值(读起来很凹口是不是?多读几遍就好了)。同时,还要给每条边都加上反向边。重复寻找,直到找不到新的路径,我们就获得了这个图的最大流。

注意,

  1. 每次寻找增广路径后,我们都会将原图更改,这样,我们会得到一个新的图。
  2. 在获得最大流的图之前,我们获得的每张图都称为残余网络。原始图也可以视为残余网络。

 

以上讲的是寻找最大流的思想。

但是,寻找增广路径的方法不止一种。

 

我最直接想到的方法,使用dfs多次搜索这张图,直到找不到为止。

 1 #include <cstdio>
 2 #include <cmath>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int N = 210;
 8 const int M = 10000010;
 9 
10 int n, m;
11 int mp[N][N];               //保存地图信息,有向图
12 bool vis[N][N];             //dfs时标记使用
13 int ans;                    //最终结果
14 
15 void init()
16 {
17     memset(mp, 0, sizeof(mp));
18     memset(vis, 0, sizeof(vis));
19     for(int i = 0; i < n; i++)
20     {
21         int a, b, c;
22         scanf("%d%d%d", &a, &b, &c);
23         mp[a][b] += c;
24     }
25     ans = 0;
26 }
27 
28 int dfs(int x, int maxn)
29 {
30     if(x == m)
31     {
32         ans += maxn;        //每次走到汇点后增加的流量
33         return maxn;
34     }
35     int flow = 0;
36     for(int i = 1; i <= m; i++)
37     {
38         if(mp[x][i] > 0 && !vis[x][i])
39         {
40             vis[x][i] = 1;
41             int mmaxn = maxn < mp[x][i] ? maxn : mp[x][i];      //取本路径中所可以通过的最小值
42             int mid;
43             if(mmaxn > 0) mid = dfs(i, mmaxn);                  //如果此路仍然是通路,则继续搜索
44             if(mid > 0)
45             {
46                 mp[x][i] -= mid;                        //已经经过的路要减去耗费的流量
47                 mp[i][x] += mid;                        //反向路(弧)增加耗费的流量
48                 maxn -= mid;                            //走过一条通路后剩余的流量
49                 flow += mid;                            //已经消耗的流量
50                 if(maxn == 0) break;
51             }
52         }
53     }
54     return flow;
55 }
56 
57 int main()
58 {
59     //freopen("test.in", "r", stdin);
60     while(~scanf("%d%d", &n, &m))
61     {
62         init();
63         while(dfs(1, M) > 0) memset(vis, 0, sizeof(vis));
64         printf("%d\n", ans);
65     }
66     return 0;
67 }
dfs

 

但是后来我突然想到。dfs找到的不一定最短路,每次搜索可能会浪费时间,然后又改成了bfs,这种方法也就是常说的Edmonds-Karp算法。

 1 #include <cstdio>
 2 #include <cmath>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <queue>
 6 using namespace std;
 7 
 8 const int N = 210;
 9 const int M = 100000010;
10 
11 int mp[N][N];
12 int fm[N];              //用于记录路径
13 int val[N];
14 int n, m;
15 int ans;
16 
17 void init()
18 {
19     memset(mp, 0, sizeof(mp));
20     for(int i = 0; i < n; i++)
21     {
22         int a, b, c;
23         scanf("%d%d%d", &a, &b, &c);
24         mp[a][b] += c;
25     }
26     ans = 0;
27 }
28 
29 void work()
30 {
31     while(1)
32     {
33         memset(val, 0, sizeof(val));
34         val[1] = M;
35         queue<int> que;
36         que.push(1);
37         while(!que.empty())             //spfa,寻找最短路
38         {
39             int k = que.front();
40             que.pop();
41             if(k == m) break;
42             for(int i = 1; i <= m; i++)
43             {
44                 if(!val[i] && mp[k][i] > 0)
45                 {
46                     fm[i] = k;
47                     que.push(i);
48                     val[i] = val[k] < mp[k][i] ? val[k] : mp[k][i];
49                 }
50             }
51         }
52         if(val[m] == 0) break;                  //当前图上找不到源点到汇点的通路,则退出
53 
54         for(int i = m; i != 1; i = fm[i])
55         {
56             mp[fm[i]][i] -= val[m];             //经过的路径上要减去耗费的流量
57             mp[i][fm[i]] += val[m];             //反向路径(弧)增加相应的路径
58         }
59         //printf("%5d\n", val[m]);
60         ans += val[m];                          //结果增加新增的流量
61     }
62 }
63 
64 void outit()
65 {
66     printf("%d\n", ans);
67 }
68 
69 int main()
70 {
71     while(~scanf("%d%d", &n, &m))
72     {
73         init();
74         work();
75         outit();
76     }
77     return 0;
78 }
bfs

 

 

接下来又找到了一种看起来很高大上的方法——Dinic算法。

这个方法要说一说,因为我也花了不少时间来理解,虽然还没有完全理解,但是已经被它所包含的思想震撼了。

这个算法是一层一层搜索的。简单来说,就是:

1)将这个图中用bfs遍历一遍,严格确立每个点的层次。

如果使用bfs可以从源点走到汇点,那么执行2),否则这张图中不存在新的增广路,算法结束。

2)从源点开始dfs,寻找到当前图中所有从源点到汇点的路径,在寻找时,严格按照点的层次寻找,只能从第i层的点走到第i+1层的点。

3)重复1)。

好神奇的方法。

  1 #include <cstdio>
  2 #include <cmath>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <queue>
  6 using namespace std;
  7 
  8 const int N = 210;
  9 const int M = 100000010;
 10 
 11 int mp[N][N];
 12 int dis[N];
 13 int cur[N];
 14 bool vis[N];
 15 int n, m;
 16 int ans;
 17 
 18 void init()
 19 {
 20     memset(mp, 0, sizeof(mp));
 21     for(int i = 0; i < n; i++)
 22     {
 23         int a, b, c;
 24         scanf("%d%d%d", &a, &b, &c);
 25         mp[a][b] += c;
 26     }
 27     ans = 0;
 28 }
 29 
 30 bool bfs()
 31 {
 32     memset(vis, 0, sizeof(vis));
 33     queue<int> que;
 34     que.push(1);
 35     dis[1] = 0;
 36     vis[1] = 1;
 37     while(!que.empty())
 38     {
 39         int k = que.front();
 40         que.pop();
 41         for(int i = 1; i <= m; i++)
 42         {
 43             if(!vis[i] && mp[k][i] > 0)
 44             {
 45                 vis[i] = 1;
 46                 dis[i] = dis[k]+1;
 47                 que.push(i);
 48             }
 49         }
 50     }
 51     return vis[m];
 52 }
 53 
 54 int dfs(int x, int val)
 55 {
 56     if(x == m) return val;
 57     int flow = 0, minn;
 58     for(int& i = cur[x]; i <= m; i++)       //随着i的变化改变cur[x],这样可以节省当前图中下次使用x时耗费的时间
 59     {
 60         int mval = val < mp[x][i] ? val : mp[x][i];     //记录当前路径中的最小的边的权,最后要根据它建立反向边
 61         if(dis[x]+1 == dis[i])
 62         {
 63             minn = 0;
 64             if(mval > 0) minn = dfs(i, mval);
 65             if(minn > 0)
 66             {
 67                 mp[x][i] -= minn;
 68                 mp[i][x] += minn;
 69                 flow += minn;
 70                 val -= minn;
 71                 if(val == 0) break;
 72             }
 73         }
 74 
 75     }
 76     return flow;
 77 }
 78 
 79 void work()
 80 {
 81     while(bfs())        //如果存在增广路,则dfs寻找,否则结束
 82     {
 83         for(int i = 1; i <= m; i++) cur[i] = 1;
 84         ans += dfs(1, M);
 85     }
 86 }
 87 
 88 void outit()
 89 {
 90     printf("%d\n", ans);
 91 }
 92 
 93 int main()
 94 {
 95     //freopen("test.in", "r", stdin);
 96     while(~scanf("%d%d", &n, &m))
 97     {
 98         init();
 99         work();
100         outit();
101     }
102     return 0;
103 }
Dinic

 

posted @ 2015-10-07 21:00  mypride  阅读(560)  评论(0编辑  收藏  举报