[待补充][进阶数据结构/图论]网络流(Network-flows)

 一、基本定义                       有向图G = (V,E,F)中

1、边e(u,v)

① 具有固定价值c(u,v),称为最大流量,也可以称为容量,指这条边最多可以运输c(u,v)点价值;

 

② 具有非固定价值f(u,v),称为当前流量,其中理论上应该遵守流量限制(Capacity Constraints):

                      0<= f(u,v) <= c(u,v)

 

斜对称性(Skew Symmetry): 对于同一条边e(u,v),有:

                      f(u,v) = -f(v,u)

这一点对于理解残留网络以及增广路非常重要!

 

 

2、点(vertex)

流量守恒 (Flow Conservation):对于非源点、汇点,应该保证所连入边总流量 = 出边总流量,按该点为x,入边的起点为u,出边的终点为v,则公式为

                                                        ∑f(u,x) = ∑f(x,v)

 

也可以理解为净流量(Net Flow) ∑f(u,v) = 0 其中u为该点,v为与u连接的所有点(包括入u点边的起点)

 

②具有两个特殊点:源点s(Source),汇点t(Toghter)。

一般来讲,f(u,s) = 0,f(t,v) = 0,也就是无法流入汇点,无法流出源点,所以 f(s,v)与 f(u,t)可以为任意值,但这两值相等 即

                    ∑f(s,v) = ∑f(u,t)

注意一点,由于其他的点都得保证流量守恒,所以最大流中与源点s相连接的边的流量f(s,v)并非完全等于c(s,v),要根据不同路径而定。

 

 

3、可行流

指从源点s 到 t的所有边都保证 0 <= f(u,v) <=c(u,v),则称这该网络流为可行流。

 

其中,零流(所有f(u,v) = 0)属于一种可行流

 

 

4、流量值

指流出源点s 或者 流入汇点t 的总值,用单个 f 来表示

                                     f = f(s,v) = f(u,v)

其中,保证所有流为可行流时,f的最大值称为最大流

 

 

5、一些性质

① 最大流与最短路的区别:最大流可以是多条路径从s流向t,且一条路径中的边权值并非累加,而是互相限制

 

而最短路是一条路径从起点走向终点中所有经历的边的 权值 累加。

 

 

②、很明显在最大流中,从s到t的一种路径中的流量受到该路径的最小容量边的限制,且该最小容量的边的流量等于该容量,即

                    min_f(u,v) = min_c(u,v)

 

若该路径为链状(没有支路),则所有边的流量都等于最小容量边的容量。如图所示

 图中最小容量边为 e(1,2) ,其f(1,2) = c(1,2) = 3,且其他边 f(u,v) = c(1,2) =3

该图也可以证明最大流中与源点s相连接的边的流量f(s,v)并非完全等于c(s,v) *(注:一 2 ②)

 

 

二、残留网络(Residual Network) Gf = (V,Ef,Ff)

1、点

所有点包括源点与汇点都与原先图完全相同

残留网络的流量值为 ff

 

 

2、边

① 一方面包括剩余容量(Residual Network)

                  cf(u,v) = c(u,v) - f(u,v)

在增广路中指剩余的容量,也就是还没有用的容量

 

② 另一方面包括反向

                  cf(v,u) = f(u,v)

在增广路中指对边权值选择的回溯

 

 

3、残留网络的一些性质

①、若残留网络Gf的最大流为max_ff,则在原图G中流量 f = f + max_ff 依然为可行流

准确来说,设 0 < ff <= max_ff,f = f + ff 依然为可行流

 

②、若残留网络Gf的最大流 max_ff = 0,则原图G的流量值 f 为最大流

 

 

4、增广路(Augmenting Path)

在残留网络Gf中从s沿着 cf(u,v) > 0注意不能等于0)的边走,若可以走到t,则称这一条从s到t的路径为增广路 (针对一条路径而言)

因此如果找不到增广路,即残留网络的最大流 max_ff = 0,则原图G的流量值为最大流。

因此只需要不断找到增广路,直到找不到则可以找到最大流。

 

 

 

5、增广路的一些性质

①、设此时增广路所遍历的流量为f' (并不一定等于残留网络的最大流) ,很明显该增广路中最大流 max_f' 应该等于所遍历边的最小残留容量 min_cf(u,v),即

                  max_f' = min_cf(u,v)

 

②、若残留网络没有建立反向边,则没有增广路并不代表原图是最大流,如下图所示(1为s,4为t)

 

 

③、由于f(u,v) = -f(v,u),所以在反向边的增广路中 f(u,v) = f(u,v) + cf(v,u) = f(u,v) - cf(u,v),也就是相当于回溯。

这个操作会使这条边的流量减少,但是既然有增广路,总体流量不会减少反而增加这条边的流量减少可以让流入其他边的流量。

也就是说,存在增广路并扩展只会让原图的流量值增加 (针对整个图而言,并非单个边),即

                    | f + ff | = | f | + | ff |

 

如上图残留网络

从 1 到 4 沿 cf > 0 的边走,明显 1 → 3 → 2 →4 , ff = 2,则可扩展为: 

 

 f = f + cf = 4 + 2 = 6

 

 

三、割

 待补充。。

 

四、最大流算法

1、EK算法

基本思想:利用bfs不断寻找增广路,直到找不到。

由于寻找增广路是在残留网络中进行,所以只需要建立残留网络即可。

一些细节:

  • 遍历一个点一方面判断这个点是否遍历过(val[v] = true),另一方面判断这个边的容量是否为0( !edge[i].c )

 

  • 寻找反向边:x ^ 1结果都是成对的 如0 ^ 1 = 1,1 ^ 1 = 0,依次类推有 2和3,4和5,6和7成对...

  因此从cnt = 0开始建边,cnt + 1为反向边,因此每次更新残留网络的时候用 edge[i] 减少流量, edge[i ^ 1]来增加流量表示回溯。

  但是要注意前向星存边初始化 head[u] = -1而不是0,此时可以用 ~i 来判断是否为 -1 ( ~i = -(i - 1),也就是说 ~(-1) = 0)

 

  • 在结束bfs的时候,queue并不一定为empty,若为结束后empty则不存在增广路。(此时可以定义bfs为bool型,遍历到t返回true,到empty都遍历不到就返回false)

  另外bfs需要记录点是否已经遍历(boolen数组val),下次bfs要重新记录是否已经遍历来重新寻找增广路

  也就是说每次bfs要注意初始化 val 与 queue

  若queue不初始化会死循环;若val不初始化增广路会找不全。

 

  • 每次寻找增广路同时需要寻找增广路的最大流量值,每个点记录到达该点的最小边容量值,公式为 les[v] = min(c[edge[i].c],les[u]),只需要初始化 les[s] = INF即可。最后 les[t] = max_f',即增广路的最大流量。

 

  • 每次遍历到一个点需要记录到达这个点的边,用pre[u]表示,然后从 t → s开始更改边与反向边的容量,此时找到这个边的起点一方面可以在建边的时候记录起点,另一边可以利用建立的反向边( i^1 为反向边)

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 #define gc(a) a=getchar()
 5 #define pc(a) putchar(a)
 6 ll read(){
 7     char c;ll x=0;bool flag=0;gc(c);
 8     while(c<'0'||c>'9'){if(c=='-') flag=1;gc(c);}
 9     while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48),gc(c);}
10     return flag?-x:x;
11 }
12 void pr(ll x){
13     if(x<0){x=-x;pc('-');}
14     if(x>9) pr(x/10);
15     pc(x%10+48);
16 }
17 //------------快读-----------
18 const int maxn = 2050;
19 const int maxm = 10050;
20 const int INF = 2147483647;
21 long long ans = 0,les[maxn];
22 int cnt = -1;                   //从-1开始建边
23 int head[maxn];
24 int s,t,n,m;                    //源点,汇点,点个数,边个数
25 int pre[maxn];                  //前驱边
26 bool vis[maxn];                 //记录点是否在队列中
27 queue<int> que;
28 
29 struct Edge{                    //建立残留网络
30     int v,nex;
31     long long c;
32     
33     Edge(){
34         this->v = 0;
35         this->nex = -1;
36         this->c = 0;
37     }
38     Edge(int v,int nex,long long c):
39     v(v),nex(nex),c(c){}
40 }edge[maxm * 2];
41 
42 void edgepush(int u,int v,long long c){
43     edge[++cnt] = Edge(v,head[u],c);          //残留容量等于初始容量
44     head[u] = cnt;
45     edge[++cnt] = Edge(u,head[v],0);          //反向边容量等于0
46     head[v] = cnt;
47 }
48 
49 bool bfs(){
50     int u,v;
51     for(int i = 1;i <= n;i++)
52         vis[i] = 0;
53     while(!que.empty()) que.pop();             //队列以及vis初始化
54     que.push(s);                               //从源点开始
55     les[s] = INF;
56     vis[s] = 1;
57     while(!que.empty()){
58         u = que.front();
59         que.pop();
60         for(int i = head[u]; ~i ;i = edge[i].nex){     //遍历与u相连的边
61             v = edge[i].v;
62             if(vis[v] || !edge[i].c) continue;         //两个都非常重要!
63             pre[v] = i;                                //记录前驱边
64             les[v] = min(les[u],edge[i].c);            //更新增广路流量
65             if(v == t) return 1;                       //如果到达汇点,说明存在增广路
66             que.push(v);                               //压入队列,这俩写在if(v == t)上面下面都一样
67             vis[v] = 1;
68         }
69     }
70     return 0;
71 }
72 
73 void EK(){
74     while(bfs()){                                     //bfs返回1则存在增广路
75         int ite = 0;
76         ans += les[t];                                // 流量只加不减
77         for(int i = t;i != s;i = edge[pre[i]^1].v){   //pre[i]^1为该边的反向边
78             ite = pre[i];
79             edge[ite].c -= les[t];
80             edge[ite^1].c += les[t];
81         }
82     }
83 }
84 int main(){
85     int u,v;
86     long long c;
87     n = read(),m = read(),
88     s = read(),t = read();
89     for(int i = 1;i <= n;i++)
90         head[i] = -1;
91     for(int i = 0;i < m;i++){
92         u = read(),
93         v = read(),
94         c = read();
95         edgepush(u,v,c);
96     }
97     EK();
98     pr(ans);
99 }

 

posted @ 2021-12-18 00:13  蒟蒻zExNocs  阅读(282)  评论(0)    收藏  举报