【腾讯文档】网络流初步

网络流初步

文章目录

  • 网络流初步
    • 一、网络流简介
      • 1. 网络
      • 2. 流
      • 3. 再次理解网络流
    • 二、常见题型(三种)
    • 三、相关问题对应算法介绍
      • 1.最大流
        • (1) FF算法 - Ford-Fulkerson算法
        • (2)EK算法 - Edmonds-Karp增广路算法
        • (3)Dinic算法
        • (4)ISAP算法
        • 总结
      • 2.最小割
        • 最大流最小割定理
          • 割(CUT)
      • 3.费用流

问题:

一、网络流简介

网络流是算法竞赛中的一个重要的模型,它有两个部分:网络

图片来源

1. 网络

网络就是一张有向图 G = (V,E)。(G是Graph、V是Vertex、E是Edge)

特别的,它拥有一个源点(Source)和一个汇点(Sink),在上图中,1是源点,3是汇点。

有向图中的边权称为容量(Capacity)用C(u,v)表示,在上图中1->2的容量表示为C(1,2) = 3。

特别的,当两点之间没有边相连时,有C(u,v) = 0。

2. 流

,就像是水流。如果把网络想象成一个自来水管道网络,那流就是其中流动的水。每条边上的流不能超过它的容量,并且对于除了源点和汇点外的所有点(即中继点),流入的流量都等于流出的流量。

设f = (u,v),其中u和v均属于V,u和v均是V集合中的元素,流满足下面三条性质

  1. 容量限制:对于每条边,流经该边的流量不得超过该边的容量,即
    对任意u,v∈V,f(u,v)≤c(u,v)对任意u,v∈V,f(u,v) ≤c(u,v) 对任意u,v∈V,f(u,v)≤c(u,v)

  2. 斜对称性:每条边的流量与其相反边的流量之和为 0,即
    对任意u,v∈V,f(u,v)=−f(v,u)对任意u,v∈V,f(u,v) = -f(v,u) 对任意u,v∈V,f(u,v)=−f(v,u)

  3. 流守恒性:除了源点S汇点T外,从源点流出的流量等于汇点流入的流量

∑f(u,v)=0,(u,v)∈E∑f(u,v)=0,(u,v)∈E ∑f(u,v)=0,(u,v)∈E

那么f称为网络G的流函数,f(u,v)称为边的流量,C(u,v) - f(u,v) 称为边的剩余容量,整个网络的流量为从源点发出的所有流量之和

图片来源

3. 再次理解网络流

网络是一张带权有向图。我们把它比喻成“自来水管道”,

源点S是大水库,想输出多少就输出多少。

但是,想要输出到目的地也就是汇点T,需要经过中继点。中继点不产生新流量、也不私吞;接受多少流量,就同时输出多少流量

点与点之间管道的容量是有限的;一条边上的流量不能超出其容量,容量很可能是有残余的。因为这些边上的限制,源头并不能无限输出。

二、常见题型(三种)

  • 最大流【模板】网络最大流

    • 有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点)。
    • FF算法、EK增广路算法、Dinic算法、ISAP算法、HLPP算法。
  • 最小费用最大流【模板】最小费用最大流

    • 每条边都有一个费用,代表单位流量流过这条边的开销。我们要在求出最大流的同时,要求花费的费用最小。
  • 最小割

    • 割其实就是删边的意思,当然最小割就是割掉X条边来让S跟T不互通。我们要求X条边加起来的流量总和最小。这就是最小割问题。

三、相关问题对应算法介绍

1.最大流

[问题概述] 网络最大流

给出一个网络图,以及其源点和汇点,求出其网络最大流。

(1) FF算法 - Ford-Fulkerson算法

该算法是不断用dfs寻找增广路,是一个暴力的算法,效率是所有相关算法里最差的,要求理解,因为后续的算法都是在它上面改进升级的。

(此部分参考链接,只做了些修改)FF算法讲解-知乎

FF算法核心在于寻找增广路(Augmenting Path)来更新最大流。

何谓增广路?例如下图中我首先选择1->2->3,这是一条增广路,提供2流量;

然后我们相应地扣除选择路径上各边的容量,1->2的容量变成1,2->3的容量变成0,这时的容量称为残余容量

然后我们再找到1->2->4->3这条路径,按残余容量计算流量,它提供1流量(选择这两条路的顺序可以颠倒)。1->2->4->3也是一条增广路。

增广路,是从源点到汇点的路径,其上所有边的残余容量均大于0。FF算法就是不断寻找增广路,直到找不到为止。这个算法一定是正确的吗?好像不一定吧,比如这张图……

如果我们首先找到了1->2->3->4这条边,那么残余网络会变成这样:

现在已经找不到任何增广路了,最终求得最大流是1。但是,很明显,如果我们分别走1->3->4和1->2->4,是可以得到2的最大流的。

为了解决这个问题,我们引入反向边。在建边的同时,在反方向建一条边权为0的边:

我们仍然选择1->2->3->4,但在扣除正向边的容量时,反向边要加上等量的容量。

这时我们可以另外找到一条增广路:1->3->2->4。

其实可以把反向边理解成一种撤销,走反向边就意味着撤回上次流经正向边的若干流量,这也合理解释了为什么扣除正向边容量时要给反向边加上相应的容量:反向边的容量意味着可以撤回的量。

加入了反向边这种反悔机制后,我们就可以保证,当找不到增广路的时候,流到汇点的流量就是最大流。

算法模板

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10010, E = 200010;
int n, m, s, t;
ll head[N];
ll to[E], nxt[E], val[E];
int tot = 1;//反向边操作,起始为1
bool vis[N];//限制增广路不要重复走点,否则很容易爆栈
void addE(int u, int v, ll w) {to[++tot] = v, val[tot] = w;//真实数据nxt[tot] = head[u], head[u] = tot; //在表头x处插入
}
ll dfs(int u, ll flow)
{//注意,在走到汇点之前,无法得知这次的流量到底有多少 if (u == t)//走到汇点才return一个实实在在的流量 return flow;vis[u] = true;for (int i = head[u]; i; i = nxt[i]) {int v = to[i];if (val[i] == 0 || vis[v])//无残量,走了也没用 continue;int res = 0;if ((res = dfs(v, min(flow, val[i]))) > 0) {//顺着流过去,要受一路上最小容量的限制val[i] -= res;//此边残余容量减小val[i ^ 1] += res;//以后可以顺着反向边收回这些容量,前提是对方有人了 return res;}}return 0;//我与终点根本不连通
}
int main()
{scanf("%d%d%d%d", &n, &m, &s, &t);while(m--){int u, v; ll w;scanf("%d%d%lld", &u, &v, &w);addE(u, v, w);addE(v, u, 0);//反向边初始化为0}ll res = 0, maxflow = 0;while (memset(vis, 0, sizeof(vis)) && (res = dfs(s, 1e18/*水库无限*/)) > 0)maxflow += res;//进行若干回合的增广printf("%lld\n", maxflow);return 0;
}

(2)EK算法 - Edmonds-Karp增广路算法

该算法是不断用BFS寻找增广路,直到网络上不存在增广路为止。

在每轮寻找增广路的过程中,EK算法只考虑图中所有 f(x,y)<c(x,y)的边,用BFS找到任意一条从S到T的路径,同时计算出路径上各边的剩余容量的最小值 minf(最小流量),则网络的流量就可以增加 minf。需要注意的是,当一条边的流量f(x,y)>0时,根据斜对称性质,它的反向边流量f(y,x)<0,此时必定有f(y,x)<c(y,x)。故 EK算法在 BFS 时除了原图的边集 E 之外,还应该考虑遍历 E 中每条边的反向边(这里与FF算法是一样的,就不再赘述)。

算法模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 505, M = 2*5005;//由于有反向边的存在我们要开2倍的边
int head[M];
int to[M], nxt[M], pre[M];// 记录前驱,便于找到最长路的实际方案
ll val[M], incf[M];//incf是增广路上各边最小剩余容量
int tot = 1;
int n, m, s, t;
ll maxflow;
bool vis[N];//限制增广路不要重复走点
void addE(int u, int v, ll w){//两种写法都是可以的to[++tot] = v, val[tot] = w, nxt[tot] = head[u], head[u] = tot;//正向边//to[++tot] = u, val[tot] = 0, nxt[tot] = head[v], head[v] = tot;//反向边
}
bool bfs()
{memset(vis,false,sizeof(vis));queue<int> q;q.push(s);vis[s] = true;incf[s] = 1e18;//增广路上各边最小剩余容量while(!q.empty()){int x = q.front(); q.pop();for(int i = head[x]; i; i = nxt[i])if(val[i]){int v = to[i];if(vis[v]) continue;//已经访问过了incf[v] = min(incf[x], val[i]);pre[v] = i;q.push(v);vis[v] = true;if(v == t) return true;}}return false;
}
void updata()//更新增广路及其反向边的剩余容量
{ll x = t;while(x != s){ll i = pre[x];val[i] -= incf[t];val[i ^ 1] += incf[t]; //方向边也要更新 xor 1 的技巧x = to[i ^ 1];}maxflow += incf[t];
}
int main()
{scanf("%d%d%d%d", &n, &m, &s, &t);memset(head, 0, sizeof(head));while(m--){int u, v; ll w;scanf("%d%d%lld", &u, &v, &w);addE(u, v, w); addE(v, u, 0);}maxflow = 0;while(bfs()) updata();printf("%lld\n", maxflow);return 0;
}

(3)Dinic算法

Dinic算法是对EK算法的优化,且比EK算法好敲。 Dinic算法可以解决99%的网络流算法问题,剩下的1%可以由ISAP算法或HLPP算法解决。(还解决不了可以解决出题人)

EK算法的问题:观察EF算法,它可能每次遍历整个残量网络却只找到1条增广路。

能不能一次多找几条增广路?于是Dinic算法就出现了,它的核心就是一次寻找多条增广路。

  • 分层图&DFS

根据BFS宽度优先搜索,我们知道对于一个节点x,我们用d[x]来表示它的层次,即S到x最少需要经过的边数。在残量网络中,满足d[y]=d[x]+1的边(x,y)构成的子图被称为分层图。而分层图很明显是一张有向无环图。

Dinic算法不断重复一下步骤以下步骤,直到残量网络中S不能到达T:

  1. 在残量网络上BFS求出节点的层次,构造分层图。
  2. 在分层图上DFS寻找增广路,在回溯时同时更新剩余容量(边权)。

所谓分层就是预处理出源点到每个点的距离。我们只往层数高的方向增广,可以保证不走回头路也不绕圈子。

给出一张图:

接着BFS分层:

DFS求增广路:

我们先观察一下,在残量网络上第一次DFS的时候,我们找到了1->3->7这一条增广路,贡献的流为2,之后我们更新残量网络(绿色的标记);

之后我们寻找第二条增广路,我们先找到了1->3->7,随后发现了3->7这条路上的剩余容量为0,于是接着回溯寻找到了1->2->7这一条增广路,贡献的流为3,之后我们更新残量网络;

之后我们寻找第三条增广路,我们先找到了1->3->7,随后发现了3->7这条路上的剩余容量为0,于是接着回溯寻找到了1->2->7这一条增广路,随后发现了2->7这条路上的剩余容量为0,于是回溯寻找到了1->5->7,贡献为5,之后我们更新残量网络;

之后我们寻找第四条增广路,我们先找到了1->3->7,随后发现了3->7这条路上的剩余容量为0,于是接着回溯寻找到了1->2->7这一条增广路,随后发现了2->7这条路上的剩余容量为0,于是回溯寻找到了1->5->7,随后发现了1->5这条路上的剩余容量为0,于是接着回溯找到了1->6->8->7这条路(非增广路),但是由于点8是在分层图上的第三层,与汇点属于同一层,不满足条件,随后退出。

以上是第一次BFS分层后,寻找多条增广路的模拟过程。

我们可以发现,在DFS的过程中,已经寻找到增广路的那条路径没有任何的利用价值,并且还会对后续的DFS造成影响,于是我们需要优化它,这就是当前弧优化。

  • 当前弧优化

有人将边叫做弧,具体是谁不知道,对于边的优化也叫弧优化。

对于一个节点u,当它在DFS中走到了第i条弧时,前i-1条弧到汇点的流一定已经被流满而没有可行的路线了。

那么当下一次再访问u节点时,前i-1条弧就没有任何意义了。

所以我们可以在每次枚举节点x所连的弧时,改变枚举的起点,这样就可以删除起点以前的所有弧,来达到优化剪枝的效果

对应到代码中,就是now数组。

或者说如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边。

now数组就是做head数组不能做的事情,也可以说它是Dinic算法的精髓所在。

当然,在DFS的过程中我们还有优化的地方,详细见代码。

算法模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 5005*2;
int head[M], to[M], nxt[M], d[M], now[M]/*当前弧优化*/;
ll val[M], flow, maxflow;
int tot = 1, n, m, s, t;
void addE(int u, int v, ll w){to[++tot] = v, val[tot] = w, nxt[tot] = head[u], head[u] = tot;to[++tot] = u, val[tot] = 0, nxt[tot] = head[v], head[v] = tot;
}
bool bfs()//在残量网络上构造分层图,返回是否搜索到了汇点
{memset(d,0,sizeof(d));//初始化分层queue<int> q;q.push(s);d[s] = 1; // 源点是第一层now[s] = head[s];//当前弧优化while(q.size()){int x = q.front();q.pop();for(int i=head[x]; i; i=nxt[i]){int p = to[i];if(val[i] && !d[p])//当前的剩余容量不为0 && 该节点是没有分层的,也就是新的节点{q.push(p);now[p] = head[p];//继承head的信息d[p] = d[x] + 1;//更新分层图if (p == t) return true;}}}return false;
}
int dfs(int u, ll flow)//先BFS分层,再DFS寻找增广路, 找一条链 S -> T
{//flow是u收到的流量,不一定可以全部都用掉if(u == t) return flow;ll rest = flow, k, i;//k是v真正输出到汇点的流量for(i=now[u];i && rest;i=nxt[i]){if(val[i] && d[to[i]] == d[u]+1)//仅允许流向下一层{k = dfs(to[i], min(rest, val[i]));//受到一路上最小流量的限制if(!k) d[to[i]] = 0;//剪枝,去掉增广完毕的点val[i] -= k;val[i ^ 1] += k;rest -= k;}        }now[u] = i;//当前弧优化,避免重复遍历从u出发不可扩展的边return flow - rest;
}
void dinic()
{ll flow = 0;while(bfs())//BFS分层while(flow = dfs(s, 1e18))//DFS寻找增广路maxflow += flow;
}
int main()
{scanf("%d%d%d%d", &n ,&m, &s, &t);while(m--){int u, v, w;scanf("%d%d%d", &u, &v, &w);addE(u, v, w);}dinic();cout << maxflow << endl; return 0;
}

(4)ISAP算法

ISAP是对Dinic的一个小改进,两者思路大体一致。ISAP是增广路算法中最快的一种。

Dinic算法的问题:每次DFS完后需要重新再BFS来构成分层图。

可不可以边DFS边修改点的层数?于是ISAP就出现了,它只在一开始bfs一次,之后对点层次的修改均在dfs中进行。

大体步骤如下:

  1. 从t(汇点)到s(源点)跑BFS

  2. 从s到t跑DFS

  3. 重复操作2直到出现断层(下面给出解释)

ISAP只跑一遍 BFS标记深度(是从汇点开始的!!!),然后每个点都会随着一次次 DFS而变高。

这样我们需要引进 gap数组, gap[i]表示高度为 i的点的个数。显而易见,当 gap[i]=0时会出现断层,也就是 s和 t不再联通,我们也就可以直接退出程序,停止寻找。

我们从终点向起点跑完BFS得到最初的高度。但是我们发现,此时还剩下一些流,那么我们将其高度提高,下一次遍历时,就可以把这个流推给其他边。

算法模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 5005*2;
int tot = 1, head[M], cur[M], to[M], nxt[M], dep[M], gap[M];
ll val[M];
int n, m, s, t;
void addE(int u, int v, ll w){to[++tot] = v, val[tot] = w, nxt[tot] = head[u], head[u] = tot;to[++tot] = u, val[tot] = 0, nxt[tot] = head[v], head[v] = tot;
}
void bfs()//倒着搜索
{queue<int> q; q.push(t);//汇点t点入栈 for(int i=1;i<=tot;i++) dep[i] = -1;//把深度变为-1(0会导致gap崩坏) for(int i=1;i<=tot;i++) gap[i] = 0;dep[t] = 0; //汇点深度为0 gap[0] = 1;//深度为0的点有1个 while (!q.empty()){int front = q.front(); q.pop();for (int i = head[front]; i != 0; i = nxt[i]){if (val[i] == 0 && dep[to[i]] == -1)//dep[to[i]]==-1相当于to[i]点没有搜索过 {dep[to[i]] = dep[front] + 1;++(gap[dep[to[i]]]);q.push(to[i]);}}//直到所有点都被遍历过 }return;
}//从t到s跑一遍bfs,标记深度ll dfs(int p = s, ll flow = 1e18)
{if (p == t) return flow;ll used = 0;for (int i = cur[p]; i != 0; i = nxt[i]){cur[p] = i;ll  v = to[i];if (dep[v] + 1 == dep[p] && val[i] > 0)//注意这里的条件{//如果这条边的残量大于0,且没有断层int k = dfs(v, min(val[i], flow - used));used += k;val[i] -= k;val[i ^ 1] += k;if (used == flow) return flow;}}//如果已经到了这里,说明该点出去的所有点都已经流过了//并且从前面点传过来的流量还有剩余//则此时,要对该点更改dep//使得该点与该点出去的点分隔开if (--gap[dep[p]] == 0) dep[s] = n;//出现断层,无法到达t了++dep[p];//层++ ++gap[dep[p]];//层数对应个数++return used;
}ll isap()
{bfs();//一次BFS构建分层图ll max_flow = 0;while (dep[s] < n){for(int i=1;i<=tot;i++) cur[i] = head[i];max_flow += dfs();}return max_flow;
}int main()
{scanf("%d%d%d%d", &n, &m, &s, &t);while(m--){int u, v; ll w;scanf("%d%d%lld", &u, &v, &w);addE(u, v, w);}printf("%lld", isap());return 0;
}

总结

  1. FF算法是不断用DFS找增广路,效率低下,用来了解什么是增广路。
  2. EK算法是不断用BFS找增广路, 时间复杂度为O(nm^2),可以处理1e13–1e14规模的网络。
  3. Dinic算法是是用BFS在残量网络上构造分层图,之后再DFS寻找增广路。属于主流解题算法。
  4. ISAP算法是用BFS倒着跑一遍标记深度,最后DFS边找增广路,边修改深度。属于主流解题算法。

2.最小割

给定一张无向图,求最少去掉多少个边,可以使图不连通。N<=50。

这种是比较直接的问法,但通常情况下,比赛不会问的这么直接。

最大流最小割定理

任何一个网络的最大流量等于最小割中边的容量之和,简记为“最大流 = 最小割”。

简证:假设“最小割 < 最大流”,那么割去这些边之后,因为网络流量尚未最大化,所以仍然可以找到一条S到T的增广路,与 S,T 不连通矛盾,故“最小割≥ 最大流”。如果我们能给出一个“最小割 = 最大流”的构造方案,即可得到上述定理。

求出最大流后,从源点开始沿残量网络 BFS,标记能够到达的点。E 中所有连接“已标记点 x”和“未标记点 y”的边(x,y)构成该网络的最小割。

以上是直接给出的结论,但作为一名ACMer,我们需要知道它是怎么来的,这样面对变种的题型我们才能从源头设计解决问题。

(以下过程重点参考了南京大学蒋炎岩教授的讲解)

  • 给定一张有向图G(V,E),s, t∈V

    • 问是否存在一条s —>t的路径?

对于这个问题,相信大家用脚写BFS/DFS就可以解决。但是,你并没有证明这个过程,谁告诉你BFS/DFS就是正确的?(当然大家主观判断上是对的,但我们需要严谨一点)

  • 能否针对G(V,E),s, t∈V给出一个“证明”?

在上图中V = {s, t, a, b, c, d}

我们可以很直观的感受到存在路径使得s—>t

接下来我们将b–>c和c–>t的方向反过来

那么这个时候我们可以直观的感受到是不存在一条s–>t的路径

我们可以列出以下路径(部分)

s–>t

s–>a–>t s–>b–>t s–>d–>t s–>c–>t

s–>a–>b–>t s–>d–>b–>t s–>a–>c–>t

s–>a–>b–>c–>t s–>d–>b–>c–>t

由于没有s–>t的边,在图上也没有找到s–>a–>t、s–>b–>t、s–>d–>t、s–>c–>t、s–>a–>b–>t s–>d–>b–>t s–>a–>c–>t、s–>a–>b–>c–>t、s–>d–>b–>c–>t等边,用枚举法可以证明不存在s–>t的路径。

以上证明虽然是正确的,但当点的数量n到达一定程度的时候,那么所列举的边就会到达一个很恐怖的数,对于证明并不友好,所以我们需要另选他法。

我们思考一下BFS/DFS是如何找不到路径的。

这两个算法会找到所有s可以到达的节点

  • 我们将s点可以到达的点写成一个集合S
  • 将不能到达的点写成一个集合T

假设BFS/DFS正确,一定不存在S到T的边

这里我们就可以引入一个重要的概念:割

割(CUT)

定义:有向图G(V,E)的s-t割

  • 两个集合S,T满足

    • S ∪ T = V, S ∩ T = Ø
    • s∈S, t∈T
    • 割的大小:{(u,v) ∈ E| u∈S,v∈T}
      • 就是S–>T方向穿过割的边数

通俗解释

  • 我们将节点分为两个部分,一个是包含了点s的S集合,一个是包含了点t的T集合。
  • 割就是对这些节点任意的划分成了两个部分(包含了以上的设定)。
  • 割的大小就是从S集合穿过割到达T集合的边数。

下面举几个例子帮助大家理解


我们可以理解为:当BFS/DFS“找不到路径”的时候会找到一个大小为0的割。

我们可以得出一个结论:

任何一个大小为0的s-t割(S,T)都是“找不到路径”的证明。

那么对于相比于枚举法纳法,当我们发现了一个大小为0的割,我们就可以断定s–>t是不存在路径的。

接下来我们加入割的概念走一遍FF算法,帮助大家理解记忆

我们走最坏的情况,蓝色边为走完之后形成的反向边

绿色为我们第二次走完之后形成的反向边

之后我们观察一下结果

  • 我们找到了两条不相交的路径s–>a–>b–>c–>t和s–>d–>b–>e–>t

  • 出现了大小为0的割

    所以我们在则张残量网络上找不到其它的增广路了,算法结束

当割的大小为0的时候,我们再也找不到一条增广路进行增广。

当最小割的大小为1的时候,我们可以找到一条增广路进行增广。

当最小割的大小为n的时候,我们可以找到n条增广路进行增广。

我们回到一开始的两张图中,在第一张图中,我们找到的最小割为1,则我们可以找到一条s–>t的路径

在这张图中,我们可以找到的最小割的大小为2,当然也可以找到大小为3的割,但最终能找到的不相交路径就是由最小割决定的2

  • dfs或bfs在残留网络上找到一个大小为0的割, 一定对应了原图上恰好等于路径数量的一个割
  • 割是不相交路径的上界
  • 用割去逼近最大不相交路径的数量
  • 我们希望求出所有st割中最小的那个
  • 我们求的是所有割中最小的那个
  • 最小的那个割就等于最多数量的那个路径

总的来说就是先利用反向边、增广路径求出一个不相交路径数m(下界),然后通过反证法证得最终残留网络会对应一个原图中大小为m的割(上界)。上界等于下界,m就是最优解。

这就是最大流最小割定理

最小割的问题可以转化为最大流的算法,这里就不在提供了。

3.费用流

在原有的网络的基础上,除了容量外,还有一个属性:单位费用。一条边上的费用等于流量×单位费用。我们知道,网络最大流往往可以用多种不同的方式达到,所以现在要求:在保持流最大的同时,找到总费用最少/最多的一种,费用流全名叫做最小费用最大流,一般是找最小的那个。

如下图,有很多种方法可以求到最大流3,

  • S–>a–>T (2) + S–>a–>b–>T (1)这种流法的费用是7×2+5×1=19
  • S–>a–>T (2) + S–>c–>b–>T (1)这种流法的费用则是7×2+4×1=18
  • S–>a–>T (2) + S–>b–>T (1) 这种流法的费用则是7×2+6×1=20
    • 第二种最小,第三种最大。事实上,第二种正是这个网络的最小费用最大流,第三种正是这个网络的最大费用最大流。

这个问题好解决,我们已经知道,只要建了反向边,无论增广的顺序是怎样,都能求出最大流。所以我们只需要每次都增广费用最少的一条路径即可。具体地,把Dinic算法里的BFS换成SPFA,EK算法里的BFS换成SPFA。

为什么是SPFA而不是Dijskra,因为Dijskra解决不了负边问题。

SFAP就是Dinic算法里的BFS换成SPFA。

算法模板

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn = 50050;
const int inf = 0x3f3f3f3f;
int n, m, s, t, tot = 1;
int maxflow, mincost;
int dis[maxn], head[maxn], incf[maxn], pre[maxn];//dis表示最短路,incf表示当前增广路上最小流量,pre表示前驱
bool vis[maxn];
struct Edge {int nxt, to, dis, flow;
}edge[maxn * 2];
void addE(int u, int v, int w, int dis) {edge[++tot].nxt = head[u];edge[tot].to = v;edge[tot].dis = dis;edge[tot].flow = w;head[u] = tot;
}
bool spfa()
{queue <int> q;for(int i=0;i<maxn;i++) dis[i] = inf;for(int i=0;i<maxn;i++) vis[i] = 0;q.push(s);dis[s] = 0;vis[s] = 1;incf[s] = 1 << 30;while(!q.empty()) {int u = q.front();q.pop();vis[u] = 0;for(int i = head[u]; i; i = edge[i].nxt) {if(!edge[i].flow) continue;//没有剩余流量int v = edge[i].to;if(dis[v] > dis[u] + edge[i].dis) {dis[v] = dis[u] + edge[i].dis;incf[v] = min(incf[u], edge[i].flow);//更新incfpre[v] = i;if(!vis[v]) vis[v] = 1, q.push(v);}}}if(dis[t] == inf) return 0;return 1;
}
void MCMF()
{while(spfa()) {//如果有增广路int x = t;maxflow += incf[t];mincost += dis[t] * incf[t];int i;while(x != s) {//遍历这条增广路,正向边减流反向边加流i = pre[x];edge[i].flow -= incf[t];edge[i^1].flow += incf[t];x = edge[i^1].to;}}
}
int main()
{scanf("%d%d%d%d", &n,&m,&s,&t);while(m--){int u, v, w, x;scanf("%d%d%d%d",&u,&v,&w,&x);addE(u,v,w,x);addE(v,u,0,-x);//反向边费用为-f[i]}MCMF();//最小费用最大流printf("%d %d\n",maxflow,mincost);return 0;
}

[参考来源]

  1. 网络流初步-知乎
  2. 网络流初步-OIWiki
  3. 网络流初步-洛谷
  4. 增广路定理 简证
  5. 求解最大流的四种算法介绍、利用最大流模型解题入门
  6. 最大流-最小割定理
  7. 网络流基础、最大流最小割定理以及证明最大流最小割定理
  8. 网络流题集
  9. [算法竞赛入门] 网络流基础:理解最大流/最小割定理 (蒋炎岩)_哔哩哔哩_bilibili
  10. 最小费用最大流 - 知乎

[题目练习]

  1. 序号 题号 标题 题型 题目难度
    1 P3376 【模板】网络最大流 最大流 1
    2 HDU 1532 Drainage Ditches 最大流 1
    3 HDU 3549 Flow Problem 最大流 1
    4 HDU 3572 Task Schedule 最大流+判断满流 2
    5 HDU 2732 Leapin’ Lizards 最大流 5
    6 HDU 3338 Kakuro Extension 最大流 5
    7 HDU 2883 kebab 最大流+判断满流 4
    8 HDU 3605 Escape 最大流 3
    9 HDU 4183 Pahom on Water 最大流 2
    10 HDU 4240 Route Redundancy 最大流 2
    11 HDU 3081 Marriage Match II 最大流+并查集 3
    12 HDU 3277 Marriage Match III 最大流+并查集 4
    13 HDU 3416 Marriage Match IV 最大流+最短路 4
    14 HDU 3468 Treasure Hunting 最大流+最短路 4
    15 P1361 小M的作物 最小割 1
    16 P1343 地震逃生 最小割 1
    17 P1345 奶牛的电信 最小割 2
    18 HDU 3046 Pleasant sheep and big big wolf 最小割 1
    19 HDU 1565 方格取数(1) 最小割 2
    20 HDU 1569 方格取数(2) 最小割 2
    21 HDU 3820 Golden Eggs 最小割 5
    22 HDU 1533 Going Home 费用流 1
    23 HDU 3488 Tour 费用流 2
    24 HDU 3435 A new Graph Game 费用流 2

洛谷网络流24题:网络流24题(做完上面再来做这个)

网络流 最大流 最小割 费用流相关推荐

  1. 学习笔记:网络流基础:理解最大流/最小割定理 (蒋炎岩)

    网络流基础:理解最大流/最小割定理 蒋炎岩 课程链接 有向图的基本概念: 问题引入 直观感受反例 引入重要概念: 割的示例 小结 再来一个问题 例子 可以找到一条路径的情况 可以找到两条路径的情况 问 ...

  2. hdu 4289(最小割最大流定理)

    题意:有N个城市,现在城市S出现了一伙歹徒,他们想运送一些炸弹到D城市,不过警方已经得到了线报知道他们的事情,不过警察不知道他们所在的具体位置,所以只能采取封锁城市的办法来阻断暴徒,不过封锁城市是需要 ...

  3. 图像分割经典算法--《最小割最大流》(Minimum Cut——Max Flow)

    1.算法介绍 最小割算法(Minimum Cut)是图像分割的经典算法之一,同时也在"Graph Cut"."Grab Cut"等算法中都有被使用过.最小割最大 ...

  4. HDU-1569 方格取数(2) 最小割最大流

    题义很简单,还记得方格取数(1)的时候,使用状态压缩写的,这里由于行列数太大,因此无法进行压缩.所以要运用的最小割最大流的思想来解这道题. 大概是这样分析的,题义是要我们求在一个方格内取出N个点,使得 ...

  5. Destroying The Graph 最小点权集--最小割--最大流

    Destroying The Graph 构图思路: 1.将所有顶点v拆成两个点, v1,v2 2.源点S与v1连边,容量为 W- 3.v2与汇点连边,容量为 W+ 4.对图中原边( a, b ), ...

  6. 【图割】最大流/最小割算法详解(Yuri Boykov and Vladimir Kolmogorov,2004 )

    本博客主要翻译了Yuri Boykov and Vladimir Kolmogorov在2004年发表的改进最大流最小割算法用于计算机视觉的论文:An Experimental Comparison ...

  7. 最大流最小割经典例题_最大流, 最小割问题及算法实现

    本博客采用创作共用版权协议, 要求署名.非商业用途和保持一致. 转载本博客文章必须也遵循署名-非商业用途-保持一致的创作共用协议. 由于博文中包含一些LaTex格式数学公式, 在简书中显示不好, 所以 ...

  8. 734. [网络流24题] 方格取数问题 二分图点权最大独立集/最小割/最大流

    «问题描述: 在一个有m*n 个方格的棋盘中,每个方格中有一个正整数.现要从方格中取数,使任 意2 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法. «编程任务: 对于给定 ...

  9. P2774-方格取数问题【网络流,最大流,最小割】

    正题 链接: https://www.luogu.org/problemnew/show/P2774 题意 在一个n*m的数字矩阵中取数,取得数不能相邻,求能取到的最大价值. 解题思路 最大价值,那么 ...

最新文章

  1. 再一次输给了AI,弯道急速超车、登上 Nature 封面
  2. mysql中修改字段的类型
  3. python娃娃_充气娃娃?Python告诉你到底有多爽......
  4. n阶自相关matlab代码,随机信号及其自相关函数和功率谱密度的MATLAB实现.doc
  5. Python3.4 Django MySQL MySQL-python 安装不成功解决办法 Unable to find vcvarsall.bat 错误
  6. Maven运行Selenium报错org/w3c/dom/ElementTraversal
  7. 好友助力功能php开发,微开讲_帮助中心
  8. php 控制 打印机 打印尺寸_打破常规尺寸,得实推出宽幅条码标签单据打印机新品...
  9. 从程序员到项目经理(9):程序员加油站 -- 再牛也要合群
  10. oracle启动pmon,oracle 11g pmon工作内容系列三
  11. WPS表格 JSA 学习笔记
  12. python的集成开发环境idle是有什么编写而成_Python 的集成开发环境IDLE是由( )编写而成。...
  13. JetSmartFilters: 如何制作搜索过滤器(1) 使用Jet-Engine
  14. argis加载tpk离线包
  15. T1055 判断闰年 (信息学一本通C++)
  16. iOS静态库SDK制作(包含第三方静态库)
  17. Python简易的HTTP服务器
  18. 输入框只允许输入数字字母下划线
  19. 选择消失,仅仅因一千个伤心的理由
  20. SVN入门及配置使用(多平台)

热门文章

  1. 【图形学】04 数学部分(四、放射变换)
  2. 基于python-实训基地管理系统-django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署
  3. Exchange 2010通配符SSL证书安装文档
  4. Windows网络编程 — UDP完成端口的实现
  5. IEC 61851-1 协议
  6. Python Orange入门之第一节:数据输入格式
  7. 关于手机端选择日期插件 mobiscroll
  8. win7关闭休眠_你的Windows7系统运行缓慢?给你一个Win7系统减肥攻略
  9. 中文自然语言处理可能是 NLP 中最难的?
  10. oracle 定时任务删除数据