26最大流
1.研究的问题
可以把最大流问题用货运公司的运货来模拟。有一个源点持续不断地产生新货物,并通过有限条道路运往一个汇点,每条道路有限定的容量,且进入一个节点的速度和出一个节点的速度相同。求源点到汇点的最大速率。
2.运用算法条件
容量值为非负数,对于两个节点,u,v,(u,v)与(v,u)至多存在一个,如果不连通,令c(u,v)=0,不允许自循环,图必须连通.
c(u,v)指的是容量,f(u,v)指的是流量
3.使实际情况满足条件的修改
(1)解决双向边问题
对于双向边(u,v),添加一个新的节点v’,将c(v,u)=0,连通(v,v’),(v’,u),让它们的容量等于之前的c(v,u)。
(2)解决多个源节点与多个汇点的问题
设立一个超级源节点s和超级汇点t,让超级源节点s到每个源节点的流量为无穷大,设立一个超级汇点,让每个汇点到超级汇点t的流量为无穷大。
4.Ford-Fulkerson方法
算法核心:沿着增广路径重复增加路径上的流量,直到
先引入三个概念
(1)残存网络
由原图G诱导出来的新图Gf
由那些仍有空间对流量进行调整的边构成
边cf(u,v)=c(u,v)-f(u,v)
为了表示对一个正流量(u,v)的缩减,将边(v,u)加入G,将其残存容量设置为f(u,v),一条边的反向流量最多将其正向流量抵消
(2)增广路径
一条从源节点s到汇点t的简单路径
残存容量是一条路上最小的cf(u,v)
(3)流网络的切割
最大流最小切割定理:一个流是最大流当且仅当其残存网络不包含任何增广路径。
算法详解
dfs的思想
以不存在增广路径为终止条件
找到一条增广路径后,将这条路上最小的值设为k
将这条路上每条边的值-k,每条边的反向边的值+k
最后终点指向前面各点的值之和即为答案
经典例题
模板题
hdoj1532
第一步:建图
用vector数组建图
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 123123;
#define INF 0x3f3f3f3f
struct edge//边的结构体
{
int to, cap, rev;//到达的点,边的容量,反向边
};
vector <edge> G[MAX];//二维
bool used[MAX];//dfs的时候标记是否被访问过
void add_edge(int from, int to, int cap)//建边
{
struct edge a;
//正向建立
a.to = to;
a.cap = cap;
a.rev = G[to].size();
G[from].push_back(a);
//反向建立,反向边
a.to = from;
a.cap = 0;
a.rev = G[from].size()-1;//对应正向边。
G[to].push_back(a);
}
第二步:最大流F-F算法
int max_flow(int s, int t)
{
int flow = 0;//记录要输出的最大流量
for(;;)
{
memset(used, 0, sizeof(used));//标记值清空
int f = dfs(s, t, INF);//找到此时存在的一条边的最大流量
if(f==0)//此时说明已经没有符合的条件了
return flow;//返回最大流量
flow += f;//继续加。。
}
}
第三步:dfs
int dfs(int v, int t, int f)//寻找从v到t的最大流量
{
if(v==t)//找到终点,返回这条路径上的最大流
return f;
used[v] = true;//标记访问过
for(int i=0; i<G[v].size(); i++)//遍历从v出发的每一条边
{
edge &e = G[v][i];//找到这个边
if(!used[e.to]&&e.cap>0)//如果到达的点没有被访问过,并且这条边还可以流水,有容量
{
int d = dfs(e.to, t, min(f, e.cap));//继续dfs,注意最大流量是min(此条边的容量,之前的最小的容量)
if(d>0)//如果存在这条边
{
e.cap -= d;//将容量减少
G[e.to][e.rev].cap += d;//反向边的容量增加
return d;//返回最大流量
}
}
}
return 0;//否则0
}
全部程序
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 123123;
#define INF 0x3f3f3f3f
struct edge//边的结构体
{
int to, cap, rev;//到达的点,边的容量,反向边
};
vector <edge> G[MAX];//二维
bool used[MAX];//dfs的时候标记是否被访问过
void add_edge(int from, int to, int cap)//建边
{
struct edge a;
//正向建立
a.to = to;
a.cap = cap;
a.rev = G[to].size();
G[from].push_back(a);
//反向建立,反向边
a.to = from;
a.cap = 0;
a.rev = G[from].size()-1;//对应正向边。
G[to].push_back(a);
}
int dfs(int v, int t, int f)//寻找从v到t的最大流量
{
if(v==t)//找到终点,返回这条路径上的最大流
return f;
used[v] = true;//标记访问过
for(int i=0; i<G[v].size(); i++)//遍历从v出发的每一条边
{
edge &e = G[v][i];//找到这个边
if(!used[e.to]&&e.cap>0)//如果到达的点没有被访问过,并且这条边还可以流水,有容量
{
int d = dfs(e.to, t, min(f, e.cap));//继续dfs,注意最大流量是min(此条边的容量,之前的最小的容量)
if(d>0)//如果存在这条边
{
e.cap -= d;//将容量减少
G[e.to][e.rev].cap += d;//反向边的容量增加
return d;//返回最大流量
}
}
}
return 0;//否则0
}
int max_flow(int s, int t)
{
int flow = 0;//记录要输出的最大流量
for(;;)
{
memset(used, 0, sizeof(used));//标记值清空
int f = dfs(s, t, INF);//找到此时存在的一条边的最大流量
if(f==0)//此时说明已经没有符合的条件了
return flow;//返回最大流量
flow += f;//继续加。。
}
}
int main()
{
int n,m,t;
scanf("%d",&t);
for(int T=1;T<=t;T++)
{
scanf("%d %d", &m, &n);
for(int i=0;i<m;i++)
{
G[i].clear();//注意清
}
for(int i=0; i<n; i++)
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
add_edge(a, b, c);
}
int w = max_flow(1, m);//寻找从1到m的最大流量。
printf("Case %d: %d\n",T,w);
}
return 0;
}
5.Edmonds-Karp 算法
EK算法是FF算法的优化,将dfs换成bfs
时间复杂度(v*e^2)
int delta[MaxN]; /* s到i的增广路径上残余流量最小值 */
int pre[MaxN]; /* s到i的增广路径中的上一个点 */
int r[MaxN][MaxN]; /* 残余流量 */
int s, t, n; /* 源点、汇点、点的总数 */
int bfs(){
memset(delta,0,sizeof delta); /* 未访问 */
queue<int> q; q.push(s);
while(not q.empty()){
int x = q.front(); q.pop();
for(int i=1; i<=n; ++i)
if(delta[i] == 0 and r[x][i] > 0){
delta[i] = min(delta[x],r[x][i]);
pre[i] = x;
if(i == t) return delta[t];
/* 已经找到汇点,提前退出 */
q.push(i);
}
}
return 0; /* 无法增广 */
}
int EK(){
int maxFlow = 0;
while(true){
int d = bfs();
if(d == 0) return maxFlow;
for(int i=t; i!=s; i=pre[i]){
r[pre[i]][i] -= d;
r[i][pre[i]] += d;
/* “反对称性” */
}
maxFlow += d;
}
return maxFlow;
}
6.dinic算法
时间复杂度:o(v^2*e)
EK算法的优化
它总是寻找最短的增广路径(通过结点数少),并沿着这条路径更新流。最短增广路径的长度在增广过程中始终不会变短,所以无需每次找增广路前都进行一次bfs。可以先进行一次bfs,按各个点被发现的顺序建立分层图,然后我们在进行dfs找到最短的增广路径,即增广的方向就是先被发现点指向后被发现的点。当没有新的最短增广路径时,意味着需要扩大最短增广路径的长度。此时再进行一次bfs,顺便可以检测是否还有通向汇点的路径。每一次bfs建立分层图的时间复杂度都是O(E),每一步最短增广路径的长度至少增加1,最多增加到∣V∣−1。
int d[MaxN], q[MaxN];
bool bfs(int s,int t){
for(int i=1; i<=n; ++i) d[i] = -1;
d[s] = 0; int *head = q, *tail = q;
*(tail ++) = s;
while(head != tail){
int x = *(head ++);
for(int i=1; i<=n; ++i)
if(d[i] == -1 and c[x][i] > 0){
d[i] = d[x]+1;
*(tail ++) = i;
}
}
return d[t] != -1; /* 存在增广路 */
}
int dfs_T;
int dfs(int x,int inFlow){
if(x == dfs_T) return inFlow;
int sum = 0;
for(int i=1,delta; i<=n; ++i)
if(d[i] == d[x]+1 and c[x][i] > 0){
delta = dfs(i,min(inFlow-sum,c[x][i]));
c[x][i] -= delta, c[i][x] += delta;
if((sum += delta) == inFlow) break;
}
return sum;
}
int dinic(int s,int t){
int maxFlow = 0; dfs_T = t;
while(bfs(s,t)) maxFlow += dfs(s,infty);
return maxFlow;
}
7.isap算法
时间复杂度(v^2*e)
EK算法的优化
加入了维持标记
当某一个标记的数目为0时即返回当前流量。循环停止
int c[MAXN][MAXN]; // 残留网络
int d[MAXN]; // d[]:距离标号
int vd[MAXN]; // vd[]:标号为i的结点个数
int S, T, n; /* 源、汇、顶点数 */
int dfs(int i,int inFlow){
// i:顶点, inFlow:最大有多大的流进入i
int j, sum = 0, mind = n-1, delta;
if(i == T) // 到达汇点
return inFlow; /* 返回值为有多大的流进入T */
for(j = 1;j <= n; j++) // 枚举i的邻接点
if(c[i][j] > 0) { // 如果有边到j
if(d[i] == d[j]+1){// (i,j) in E'
delta = dfs(j,min(inFlow-sum,c[i][j]));
/* inFlow-sum:在i点剩下的流量; c[i,j]:这条边的容量 */
// 递归增广,返回沿(i,j)的实际增广量
c[i][j] -= delta; // 更新残留网络
c[j][i] += delta; /* 反对称性 */
sum += delta; // sum记录已经增广的流量
if(d[S] >= n)
// 结束,向上一层返回经过i的实际增广量
return sum;
if(sum == inFlow) break;
// 已经到达可增广上界,提前跳出
}
if (d[j] < mind) mind = d[j];
// 更新最小的邻接点标号
}
if(sum == 0) { // 如果从i点无法增广
vd[d[i]] --; // 标号为d[i]的结点数-1
if(vd[d[i]] == 0) // GAP优化
d[S] = n; /* break标记 */
d[i] = mind + 1; // 更新标号
vd[d[i]] ++; // 新标号的结点数+1
}
return sum; // 向上一层返回经过i的实际增广量
}
int isap(){
int maxFlow = 0;
memset(d,0,sizeof d);
/* 显然,d全部为0是合法的 */
memset(vd,0,sizeof vd);
vd[0] = n; // all vertexes
while(d[S] < n)
maxFlow += dfs(S,INF);
return maxFlow;
}
实际应用
1、裸的最大流
2、二分图的最大匹配:建一个点S,连到二分图的集合A中;建一个点T,连到二分图的集合B中。再将所有的集合A中的点与集合B中的点相连。全部边权设为1,跑一遍最大流,结果即为二分图的最大匹配
3、最小割:在单源单汇流量图中,最大流等于最小割
4、求最大权闭合图:最大权值=正点权之和-最小割
来源:CSDN
作者:HDU_hzh
链接:https://blog.csdn.net/HDUCheater/article/details/104733864