网络流

网络及网络流


什么是网络?网络其实就是有向带权图。为什么要叫网络,是因为权值是容量,容量意味着可以在单位时间内经过的上限,但是可以比上限小。
有向图=点集+有向边集
一个实例:运输网络
这里写图片描述

网络定义


* 一个有向图 G=(V,E); V代表点的集合,E代表边的集合。*
* 有两个特别的点:源点s、汇点t;*
* 图中每条边(u,v)∈E,有一个非负值的容量C(u,v)*
记为 G=(V,E,C)
网络三要素:点、边、容量


可行流定义

是网络G上的一个“流”,即每条边上有个“流量”P(u,v),要满足下面两个条件:
流的容量限制—弧:
0PuvCuv (对于任意弧(u,v)——有向边)
流的平衡—点:
除源点和汇点,对任意中间点有:流入u的“流量”与流出u的“流量”相等。即:
这里写图片描述
网络的流量:源点的净流出“流量” 或 汇点的净流入“流量”。即:
这里写图片描述
注意,我们这里说的流量是一种速率,而不是指总量。联系上面所说的实例,下面是一个流量为1的可行流:
这里写图片描述

标准的网络流图应该是这么画的:
这里写图片描述


tips:事实上,打一个简单的比方,我们把路径看成一根根水管,水管的容积就是容量限制,通过的水流就是流量,显然通过的水流不能超过水管的容积(不然就炸水管啦!!),下面要讲的最大流问题就是水管中最大能流过的水。


最大流问题

寻找网络G上可能的最大流量(和一个有最大流量的可行流方案),即为网络G上的最大流问题。

最大流算法的核心—增广路径


我们先来看一个网络流:
这里写图片描述

退流的概念—后向弧
仔细分析上图,我们发现,流量是可以增加的:
这里写图片描述
把一个流量弧(B,C)和(C,T)上的流退回到B点,改道从B-D-E-T走,就可以增加流量了,如下图:
这里写图片描述
上图不能“直接寻找增大流的路径”,是因为当初有些弧上的流选择不“恰当”,要“退流”。
一种直观的想法就是:调整或重新搜索“当初的选择”—难!
能不能保留以前的工作,而在这之上改进呢?经过专家们研究发现,加入退流思想—后向弧,就能再次“直接寻找增大流的路径”。


增广路径(可改进路径)的定义

若P是网络中连结源点s和汇点t的一条路,我们定义路的方向是从s到t,则路上的弧有两种:
前向弧—弧的方向与路的方向一致。前向弧的全体记为P+;
后向弧—弧的方向与路的方向相反。后向弧的全体记为P-;
设F是一个可行流,P 是从s到t的一条路,若P满足下列条件:
在P+的所有前向弧(u,v)上, 0f(u,v)<C(u,v) ;
在P-的所有后向弧(u,v)上, 0<f(u,v)C(u,v) ;
则称P是关于可行流F的一条可增广路径。
这里写图片描述
本图中:S-A-C-B-D-E-T 为一增广路径。其中(C,B)为后向弧,其它为前向弧。


在增广路径上的改进算法
1) 求路径可改变量
这里写图片描述

2)修改增广路径上的流量


附加网络1—残留网络

由于要考虑前向弧、后向弧,分析、描述时不简洁,在图2.1上直观看也不容易看出增广路径。
因此我们把已经有的流量从容量中分离出来表示,引入一个与原问题等价的附加网络1:残留网络。
这里写图片描述
其中,前向弧(黑色)上的容量为“剩余容量”=C(u,v)-f(u,v);后向弧(红色双线)上的容量为“可退流量”=f(v,u)。
改造后的网如下:
这里写图片描述
在这张图上,我们找增广路径显的非常直观了!
结合增广路径,我们有如下定理:


最大流定理
如果残留网络上找不到增广路径,则当前流为最大流;反之,如果当前流不为最大流,则一定有增广路径。

(虽然我觉得这是一个很扯皮的定理,但是确实是最大流算法的核心)

证明涉及最小割概念,下文会讲……

用图示流程来表示最大流算法:
这里写图片描述


基于这种思想的算法,关键之处在于怎样找增广路径。常用方法有:
 深度搜索dfs :Ford-Fulkerson 算法,也是入门算法。
 宽度搜索bfs
 优先搜索pfs—即类似Dijkstra算法的标号法。
入门的板子这里就不给出了,这里给出Dinic算法的代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=10000010;
int read()
{
    int sum=0,flag=1;
    char c;
    for(;c<'0'||c>'9';c=getchar())if(c=='-') flag=-1;
    for(;c>='0'&&c<='9';c=getchar())sum=(sum<<1)+(sum<<3)+c-'0';
    return sum*flag;
}
int fd;
bool check=true;
struct edge
{
    int y,next,v;
}e[MAXN];
int link[MAXN];
int tot=1;
int n,m;
int d[MAXN];
int ans;
int head,tail,q[MAXN];
void insert(int x,int y,int z)
{
    e[++tot].next=link[x];e[tot].v=z;e[tot].y=y;link[x]=tot;
    e[++tot].next=link[y];e[tot].v=0;e[tot].y=x;link[y]=tot;
} 
void init()
{
    m=read();n=read();
    int x,y,z; 
    for(int i=1;i<=m;++i)
    {
        x=read();y=read();z=read();
        insert(x,y,z);
    }
}
void bfs() //按层次分层
{
    memset(d,-1,sizeof(d));
    head=tail=1;
    q[1]=1;d[1]=0;
    while(head<=tail)//开始bfs,确定每个点的level。
    {
        int k=q[head];
        for(int i=link[k];i;i=e[i].next)
            if(e[i].v&&d[e[i].y]<0)
            {
                d[e[i].y]=d[k]+1;
                q[++tail]=e[i].y;
            }
            head++;
    }
    if(d[n]!=-1) check=true;//边界,如果从s到t没有路径,已经不可增广。
}
int dfs(int k,int l)//l是上一层传过来的
{
    if(k==n) return l;
    int maxl=0;//maxl 表示当前点已经增广的最大流量。
    for(int i=link[k];i&&maxl<l;i=e[i].next)//maxl<l也是个很不错的剪枝
    //如果当前点能增广的流量比前面传过来的流量还大,表明,流量瓶颈点在前面。
    {
        if(d[e[i].y]==d[k]+1&&e[i].v)
        {
            if(fd=dfs(e[i].y,min(e[i].v,l-maxl)))
             //注意这里是l-maxl,仔细思考为什么?
            {
            maxl+=fd;
            e[i].v-=fd;
            e[i^1].v+=fd;
            }
        }
    }
    if(!maxl) d[k]=-1;//如果当前点不可增广,下一次就不会再找。可行性剪枝。
    return maxl;
}
void dinic()
{
    while(check)
    {
    check=false;
    bfs();
    if(!check) break;
    while(fd=dfs(1,MAXN)) ans+=fd;
    }
}
int main()
{
    init();
    dinic();
    printf("%d",ans);
    return 0;
}

最小割

to be continued……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值