网络最大流

我这个连kmp和rmq都不会的蒟蒻现在才勉强能看懂网络流.
唉.
关于网络流,基础知识很多博客上都有.让我也来扯会淡.

网络流是什么?

考虑以下情景.
假设我家非常有钱(当然是不存在的),自来水厂出的自来水全部都供应我家.
那么必然有很多水管从厂里伸出来,指向我家.水管要经过一些途中节点.
显然水管都是单向的.自来水厂没有人给它供应水,因此它的入度为0.
这样的点我们称为源点(s).
那么我家这样的点出度就为0,称为汇点(t).
那么这张包括源点,汇点,途中节点,以及有向边构成的图被称作网络,它是网络流的基础.
每根水管都有它的承载极限,即对于某一根水管,它的水流量不能超过某个值wi,称为最大容量.
然后比如说我们有了这张图.
这里写图片描述
1是源点,5是汇点,红色的数字是每一根水管的最大容量.

我们要做什么?

那么网络最大流的计算目标是什么呢?
很简单,就是自来水厂火力全开能运送到我家的总水流量.
在每一条路上能流过的最大水流量就是此路上经过的所有边的最小值.
注意每次寻找完一条路之后要这条路上所有边都要减掉求出的流量.

我们手膜一下这张图,会发现答案就是5.
1->3->5,总流量2;
1->2->4->5,总流量2,
1->2->3->5,总流量1.

好的.

三个定理(证明就算了)

第一个:C代表每条边的容量,F代表每条边目前的水流量,则必定有F<=C.
第二个:任意一个节点,流入水量必定等于流出水量,否则细思极恐.(源点和汇点不算,显然自来水厂的水来自哪里都无所谓.)
第三个:对于任何一条边,如果u向v流了f的流量,则v必定向u流了-f的流量.

如何求最大流

经过了刚才的铺垫,我们可以开始介绍最基本的算法:增广路算法.
根据刚才手膜的结果,我们可以发现以下的方法:
不断地寻找一条从源点到汇点的能够增加流的路径,直到找不出为止.
最多VE次增广后,增广路不存在了,即可找出最大流.(显然最多更新E次之后就找不出增广路了.)
什么是增广路?所谓增广路,是一条s-t的路径,并且上面每一条边的残留容量都为正.
可是还有一个非常重要的问题.
这样的做法是贪心,所求出的不一定是最大值,我们需要建立一些别的东西来反悔.
于是有了反向边.
每当我们让自来水厂向水管里通水之后,一条虚拟的从v流向u的边就会加上f.这样的话当你发现还有更好的结果时就可以让水从反向边里流过了.
==================================以上是基本内容===============================
很多名词我就不解释了.

游戏现在开始

首先说明一下,我这里引用了非常多别人的博客,大家在百度首页上面搜最大流的时候都是能搜到的.
由以上那个方法做主导的算法叫做EdmondsKarp算法,其核心就是进行无数多次的bfs,直到找不出增广路结束.
接下来介绍一种非常实用的算法.

Dinic算法

Dinic算法的思路是:

1.通过bfs建立分层图.
分层图的建立方法:从源点开始,寻找每一个点到源点的最短距离(边的权值视为1).注意边的剩余流量必须大于0.
2.通过dfs寻找增广路并循环直到没有增广路.
增广路不能在相同深度的节点上寻找.为什么可以这么做我不知道.
3.循环上述操作,直到bfs无法到达汇点.
4.输出答案.

代码如下.
例题:不提供链接了.
luogu p3376 模板[网络最大流]
HihoCoder-1369 网络流1

/*
默认源点为1,汇点为n.
这个代码加了一个当前弧优化.
所谓当前弧优化就是记录下当前dfs到哪一条弧,前面的弧因为fv-fu!=0不需要再考虑了.
使用当前弧优化之后快了很多.
可以先写没有当前弧优化的dinic,把代码里面的cur数组去掉,其它地方全部替换成head就可以了.
*/
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int tamate=2e4,inf=0x3f3f3f3f;//好吧,我每次写代码都改变大N的名称是有原因的.
struct node{int next,to,w;}edge[tamate<<1|15];
int cnt,head[tamate<<1|15];
void add(int u,int v,int w){edge[cnt]={head[u],v,w},head[u]=cnt++;}
int n,m,d[tamate|15],cur[tamate|15];//深度,当前弧

int bfs()//广度优先搜索出深度
{
queue<int> q;
for (;!q.empty();q.pop());
memset(d,-1,sizeof d);//深度初始化为-1
for (d[1]=1,q.push(1);!q.empty();)
  {
  int u=q.front();q.pop();
  for (int i=head[u];~i;i=edge[i].next)
    {
    int v=edge[i].to;
    if (edge[i].w>0&&!~d[v]) d[v]=d[u]+1,q.push(v);//边的容量大于0并且v还没有被搜到过
    }
  }
return ~d[n];//如果d[n]=-1,说明已经搜不到汇点,不可能再有增广路了.
}

int dfs(int u,int flow)//flow是当前流
{
if (u==n) return flow;//到汇点了就返回当前流
for (int& i=cur[u];~i;i=edge[i].next)//注意对当前弧的引用
  {
  int v=edge[i].to;
  if (d[v]==d[u]+1&&edge[i].w)//必须要严格按照bfs的顺序增广.
    {
    int dist=dfs(v,min(flow,edge[i].w));//最小的流
    if (dist>0) return edge[i].w-=dist,edge[i^1].w+=dist,dist;//找到一条增广路,把这条边去掉dist,它的反向边加上dist就可以了.
    /*
    可以看到因为我们建立正向边和反向边是紧邻着建立的,故此只要xor1就可以得到反向边.
    */
    }
  }
return 0;
}

int dinic()
{
int ans=0,d;
for (;bfs();) 
  {
  for (int i=1;i<=n;++i) cur[i]=head[i];//注意将每一个点的当前弧都处理为head[i]
  for (;d=dfs(1,inf);) ans+=d;
  }
return ans;
}

int main()
{
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for (;m--;)
  {
  int u,v,w;
  scanf("%d%d%d",&u,&v,&w);
  add(u,v,w),add(v,u,0);//建立反向边,流量为0
  }
printf("%d",dinic());
}

谢谢大家.

猜你喜欢

转载自blog.csdn.net/qq_31908675/article/details/79545880
今日推荐