算法适用问题:网络流是一种重要的组合优化问题,在现实中很多问题都可以看作是网络流问题,例如运输货物,水流问题,匹配问题,网络是一种每个边都有方向并且带权的图,一般情况下我们会把各种网络问题抽象成网络流问题,方便解题。
网络流是满足以下性质的网络:每一条边拥有一个最大的容量c,即该条路可以容纳的最大流量(这个流量可以理解成速度,而不是一共能流过多少),f是流过该边的实际流量,且总有f<=c。对于图中每个顶点(源点和汇点除外)都有流出的流量等于流入的流量。图中有两个特殊点,一个源点和一个汇点,源点是起始点,汇点是终点,且对于源点来说其流入量为0,对于汇点来说流出量为0,源点的流出量等于汇点的流入量,对于最大流问题既是要找出流入汇点的最大流量值 。打个比方,一座城市只有一个自来水场,却有很多用户,一些管道(有粗有细)将自来水场和用户连接起来,(如果一个用户1和自来水场连接,用户2和用户1连接,那么用户2也可以认为和自来水厂连接),自来水厂就可以视为源点,其中某一个用户则可以视为汇点,如果自来水场不断加大单位时间注水的量,则用户单位时间内能接受的最大水流量就是网络的最大流。
定义:
网络:有一个源点 s 和汇点 t ,每一条有向边e=(u,v)都有一个容量限制记做c(e)。
流:定义在网络弧集上的实值函数 f ,满足三个性质
(1)对任意的弧 0 <= f <= c(e),容量限制。
(2)f(u,v) == -f(v,u),反对称性。
(3)流守恒性:除源汇点外,其余顶点都是过度点,流进顶点的流总和等于流出顶点的流总和。
残余网络:用e表示网络中的边,e’ 表示残余网络中的边,残余网络中的边由以下两种构成:
1)若f(e) < c(e) ,e=(u,v),则e’ =(u,v)容量为 c(e) - f(e),表示生成的边表示沿着这条边还能推进多少流
2)若f(e)>0,e=(u,v),则加入边 e’=(v,u),容量为 f(e),表示生成的边表示沿着该边的逆方向能退回多少流。
增广路: 定义增广路 P 是在残余网络上的一条从源点 s 到汇点 t 的简单路径,路径的残余流量为该边上的边 e’ 容量的最小值,其实就是残余网络上增广的流值大于 0 的一条路径。
割的定义:设网络G,如果 X 是 V 的顶点子集,Y 是 X 的补集,即 Y = V - X,且满足 源点 属于X,汇点 属于 Y。则称K=(X,Y)为网络 G 的割,K的容量记为 cap(K) 最小割就是该网络中流量最小的割。
最小割最大流定理:指在一个网络流中,能够从源点到达汇点的最大流量 等于 如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。
求解最大流的算法:
*Dinic算法
*Edmonds-Karp算法
*Ford-Fulkerson方法
*sap算法
一.Dinic算法
算法步骤:
(1)初始化容量网络和网络流。
(2)构造残留网络和层次网络,若汇点不再层次网络中,则算法结束。
(3)在层次网络中用一次DFS过程进行增广,DFS执行完毕,该阶段的增广也执行完毕。
(4)跳到步骤(2)。
算法结束后,最大网络流求解完毕。
算法最重要的两步即(1)构造残余网络和层次网络。(2)在层次网络中使用DFS进行增广。
定义存储网络的存储结构
class Edge{
public:
int c,f;//c表示正向流量,f表示反向流量
};
Edge edge[max_V][max_V]//max_V表示节点个数
int level[max_V]//level存储节点的层次
1)构造残余网络和层次网络
使用BFS建立层次网络
bool dinic_bfs(int s,int t){//s为源点,t为汇点构建
memset(level,0,sizeof(level));//将层次网络初始化
queue<int>q;//定义队列,进行BFS
q.push(s);//将s推入队列
level[s]=1;//将源点的level置为1
while(!q.empty()){//开始BFS
int tmp=q.front();
q.pop();
for(int i=1;i<=N;i++){//搜索其余定点
if(!level[i]&&edge[tmp][i].c>edge[tmp][i].f){
//如果i点为进入层次网络,并且从tmp点到i点还有流量,
//则将i点划入层次网络,并且其层次是tmp节点的层次加一
level[i]=level[tmp]+1;
q.push(i);//将i点推如队列
}
}
}
return level[t]!=0;//返回汇点是否在当前构造的层次网络中
}
2)在层次网络中进行增广。
使用DFS进行寻找增广路
int dinic_dfs(int u,int t,int cp){//u是进行寻路的起点,t是寻路的终点,(就是汇点)
int tmp=cp;
if(u==t){
return cp;//如果已找到,则返回inf
}
for(int i=1;i<=N;i++){
if(level[u]+1==level[i]){//如果是下一个层次,则判断其是否有流量
if(edge[u][i].c>edge[u][i].f){
int x=dinic_dfs(i,t,min(tmp,edge[u][i].c-edge[u][i].f));
//在当前的基础上继续进行DFS寻路
edge[u][i].f+=x;//将从u到i的反流加上x
edge[i][u].f-=x;//将从i到u的反流减去x
tmp-=x;
}
}
}
return cp-tmp;//返回当前增广路的最大流量
}
Dinic算法核心
int dinic(int s,int t){//s为源点,t为汇点
int ans=0,tp;//ans为最大流
while(dinic_bfs(s,t)){//如果求解层次网络成功
if(tp=dinic_dfs(s,t,inf)){//则求解增广路的最大流量
ans+=tp;//最大流加上当前求解的增广路的流量
}
}
return ans;//返回最大流
}
例一
ZOJ 2706 How Many Shortest Path
地址 : http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1760
解题思路:通过所给的邻接矩阵,使用弗洛伊德算法计算最短路径,而后建立edge图,将每个流量置为1,最后使用Dinic算法计算最汇点的流量,即是最后有几条互不相交的最短路经。
代码:
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<map>
#include<queue>
#include<bitset>
#include<list>
#include<stack>
#include<iterator>
#include<fstream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
const int nMax=200;
const int mMax=100005;
const int inf=1<<29;
class Edge{
public:
int c,f;
};
Edge edge[205][205];
int level[205],N;
bool dinic_bfs(int s,int t){
memset(level,0,sizeof(level));
queue<int>q;
q.push(s);
level[s]=1;
while(!q.empty()){
int tmp=q.front();
q.pop();
for(int i=1;i<=N;i++){
if(!level[i]&&edge[tmp][i].c>edge[tmp][i].f){
level[i]=level[tmp]+1;
q.push(i);
}
}
}
return level[t]!=0;
}
int dinic_dfs(int u,int t,int cp){
int tmp=cp;
if(u==t){
return cp;
}
for(int i=1;i<=N;i++){
if(level[u]+1==level[i]){
if(edge[u][i].c>edge[u][i].f){
int x=dinic_dfs(i,t,min(tmp,edge[u][i].c-edge[u][i].f));
edge[u][i].f+=x;
edge[i][u].f-=x;
tmp-=x;
}
}
}
return cp-tmp;
}
int dinic(int s,int t){
int ans=0,tp;
while(dinic_bfs(s,t)){
if(tp=dinic_dfs(s,t,inf)){
ans+=tp;
}
}
return ans;
}
int mp[nMax][nMax];
int maz[nMax][nMax];
void floyd(int N){//使用floyd算法求解最短路径
for(int k=1;k<=N;k++){
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
maz[i][j]=min(maz[i][j],maz[i][k]+maz[k][j]);
}
}
}
}
void buildmap(int s,int t,int N){//建立edge图
memset(edge,0,sizeof(edge));
for(int i=1;i<=N;i++){
if(maz[s][i]==inf){
continue;
}
for(int j=1;j<=N;j++){
if(i==j||maz[j][t]==inf||maz[i][j]==inf){
continue;
}
if(maz[s][t]==maz[s][i]+mp[i][j]+maz[j][t]){
edge[i][j].c=1;
}
}
}
}
int main(){
int i,j,s,t;
while(~scanf("%d",&N)){
for(i=1;i<=N;i++){
for(j=1;j<=N;j++){
scanf("%d",&maz[i][j]);
if(maz[i][j]==-1){
maz[i][j]=inf;
}
if(i==j){
maz[i][j]=0;
}
mp[i][j]=maz[i][j];
}
}
scanf("%d%d",&s,&t);
s++;t++;
if(s==t){
cout<<"inf"<<endl;
continue;
}
floyd(N);
buildmap(s,t,N);
cout<<dinic(s,t)<<endl;
}
return 0;
}