求解最大流的整体思路是福特福克森算法。该算法的思想是:
在残余网络中找可增广路,然后在实流网络中沿着可增广路增流,在残余网络中沿着可增广路减流,;继续在残余网络中找可增广路。直到不存在可增广路位置。此时,实流网络中的可行流就是所求的最大流.
增广路定理:设flow是网络G的一个可行流,如果不存在从源点S到汇点t关于flow的可增广路p,则flow是G的一个最大流
福特福克森也是能算是一种求解最大流的思想,并不是具体的算法。下面说一下具体实现的算法。
实现的算法有三种:EK算法,Dinic算法,SAP算法+gap优化
我说一下EK算法和SAP算法。
用邻接矩阵存储残余网络,利用一个队列q来存放已访问未检查的点,vis[]数组来标记当前点是否被访问过,pre[]数组来记录前驱节点。
利用BFS来寻找可增广路,当存在一条可增广路时。从汇点一直往前面找,一直找到起点,在路径上找到一条可增广量最小的边,然后从起点到汇点,残余网络中正向沿着可增广路增流,反向沿着可增广路减流,然后不断地进行BFS,把每一次的可增光量累加起来,就是最大流
复杂度:O(V∗E2)
代码:
可以AC:POJ1273 Drainage Ditches(网络流–最大流,EK增广路算法)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int N=200+20;
int g[N][N],f[N][N],pre[N];//分别代表残余网络,实流网络,前驱数组
bool vis[N];//标记数组
int n,m;//点数和边数
bool bfs(int s,int t)
{
mem(pre,-1);
mem(vis,false);
queue<int>q;
vis[s]=true;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=1; i<=n; i++)
{
if(!vis[i]&&g[now][i]>0)
{
vis[i]=true;
pre[i]=now;
if(i==t)
return true;
q.push(i);
}
}
}
return false;
}
int EK(int s,int t)
{
int v,w,d,maxflow=0;
while(bfs(s,t))
{
v=t;
d=inf;
while(v!=s)
{
w=pre[v];
d=min(d,g[w][v]);
v=w;
}
maxflow+=d;
v=t;
while(v!=s)
{
w=pre[v];
g[w][v]-=d;
g[v][w]+=d;
if(f[v][w]>0)
f[v][w]-=d;
else
f[w][v]+=d;
v=w;
}
}
return maxflow;
}
int main()
{
int a,b,c;
while(~scanf("%d%d",&m,&n))
{
mem(g,0);
mem(f,0);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&a,&b,&c);
g[a][b]+=c;
}
printf("%d\n",EK(1,n));
}
return 0;
}
在这个算法中,我们用邻接表存储混合网络(正向边显示,cap,flow,反向边也显示这个).
这个算法的思想是,我们给广搜的网络标一个高度,每一层一个高度,最后一层的高度是0,然后按照广搜的层次,到起点依次递增。然后搜索的时候直接按照标签的告诉从高到低找,这样可以加快效率,当走的点的标签高度走不动的时候,我们采取重贴标签的策略,令当前节点的高度=所有邻接点高度的最小值+1,如果没有邻接边,则令当前节点的高度=节点数(注意:一定要保证容量>流量),然后不断的进行贴标签,重贴标签的过程,累积可增广量,最后的值就是最大流
我们需要进行gap优化:利用一个数组g[]来记录,当前的高度的节点有多少个,当重贴标签后发现当前高度的点只有一个那么就可以提前结束程序
算法复杂度:O(V2∗E)
下面是代码:
可以AC: P3376 【模板】网络最大流
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int N=10000+20;
const int M=100000+20;
int top;
int h[N],pre[N],g[N];//h[i]记录每个节点的高度,pre[i]记录前驱,g[i]表示距离为i个节点数有多少个
int first[N];
struct node
{
int v,next;
int cap,flow;
} E[M*2];
void init()
{
mem(first,-1);
top=0;
}
void add_edge(int u,int v,int c)
{
E[top].v=v;
E[top].cap=c;
E[top].flow=0;
E[top].next=first[u];
first[u]=top++;
}
void add(int u,int v,int c)
{
add_edge(u,v,c);
add_edge(v,u,0);
}
void set_h(int t)//标高函数,从终点向起点标高
{
queue<int>q;
mem(h,-1);
mem(g,0);
h[t]=0;
q.push(t);
while(!q.empty())
{
int v=q.front();
q.pop();
g[h[v]]++;//当前高度的个数+1
for(int i=first[v]; ~i; i=E[i].next)
{
int u=E[i].v;
if(h[u]==-1)//当前节点未标高
{
h[u]=h[v]+1;
q.push(u);
}
}
}
}
int Isap(int s,int t,int n)//isap算法
{
set_h(t);
int ans=0,u=s;
int d;
while(h[s]//节点的高度小于顶点数
{
int i=first[u];
if(u==s) d=inf;
for(; ~i; i=E[i].next)
{
int v=E[i].v;
if(E[i].cap>E[i].flow&&h[u]==h[v]+1)//容量大于流量且当前的高度等于要去的高度+1
{
u=v;
pre[v]=i;
d=min(d,E[i].cap-E[i].flow);//找最小增量
if(u==t)//到达汇点
{
while(u!=s)
{
int j=pre[u];//找到u的前驱
E[j].flow+=d;//正向流量+d
E[j^1].flow-=d;//反向流量-d
u=E[j^1].v;//向前搜索
}
ans+=d;
d=inf;
}
break;
}
}
if(i==-1)//邻接边搜索完毕,无法行进
{
if(--g[h[u]]==0)//重要的优化,这个高度的节点只有一个,结束
break;
int hmin=n-1;//重贴标签的高度初始为最大
for(int j=first[u]; ~j; j=E[j].next)
{
if(E[j].cap>E[j].flow)
hmin=min(hmin,h[E[j].v]);//取所有邻接点高度的最小值
}
h[u]=hmin+1;//重新标高
g[h[u]]++;//标高后该高度的点数+1
if(u!=s)//不是源点时,向前退一步,重新搜
u=E[pre[u]^1].v;
}
}
return ans;
}
int main()
{
int n,m,u,v,w,s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
init();
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
printf("%d\n",Isap(s,t,n));
return 0;
}
const int N=1000+20;
const int M=2*100000+20;
int top;
int h[N],pre[N],g[N],first[N],cur[N];//h[i]记录每个节点的高度,pre[i]记录前驱,g[i]表示距离为i个节点数有多少个
struct node
{
int v,next,cap;
} E[M];
void init()
{
mem(first,-1);
top=0;
}
void add_edge(int u,int v,int c)
{
E[top].v=v;
E[top].cap=c;
E[top].next=first[u];
first[u]=top++;
E[top].v=u;
E[top].cap=0;
E[top].next=first[v];
first[v]=top++;
}
int sap(int start,int end,int nodenum)
{
memset(h,0,sizeof(h));
memset(g,0,sizeof(g));
memcpy(cur,first,sizeof(first));
int u=pre[start]=start,maxflow=0,aug=-1;
g[0]=nodenum;
while(h[start]for(int &i=cur[u]; i!=-1; i=E[i].next)
{
int v=E[i].v;
if(E[i].cap&&h[u]==h[v]+1)
{
if(aug==-1||aug>E[i].cap)
aug=E[i].cap;
pre[v]=u;
u=v;
if(v==end)
{
maxflow+=aug;
for(u=pre[u]; v!=start; v=u,u=pre[u])
{
E[cur[u]].cap-=aug;
E[cur[u]^1].cap+=aug;
}
aug=-1;
}
goto loop;
}
}
int mindis=nodenum;
for(int i=first[u]; i!=-1; i=E[i].next)
{
int v=E[i].v;
if(E[i].cap&&mindis>h[v])
{
cur[u]=i;
mindis=h[v];
}
}
if((--g[h[u]])==0)break;
g[h[u]=mindis+1]++;
u=pre[u];
}
return maxflow;
}
在求最大流的过程中,对于每一条边都有一个费用,现在希望费用最小,流最大。求最大流和最小费用。
最小费用路算法的思想是:先找最小费用路,在该路径上面增流,增加到最大流。
利用邻接表建立双向边,正向边的花费为cost,反向边的花费为-cost.
在找最小费用路的时候,从源点出发,沿着可行 (E[i].cap>E[i].flow)广度搜索每个邻接点, 如果当前的边可以继续松弛,那么就更新,就是SPFA算法,并且记录一下前驱。
当找到最小费用路以后,那么就从汇点向源点找一条,最小的可增流量,沿着增广路正向增流,反向减流,最后花费的费用为:
可以AC:【模板】最小费用最大流
算法复杂度:O(V2∗E)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
#define inf 1000000
#define mem(a,b) memset(a,b,sizeof(a))
const int N=5000+20;
const int M=50000+20;
int top;//当前边下标
int dis[N],pre[N];//源点到点i的最小距离,pre[i]记录前驱
bool vis[N];//标记数组
int maxflow;
int first[N];//存储头结点
struct Edge
{
int v,next;
int cap,flow,cost;
} E[M*2];
void init()
{
mem(first,-1);
top=0;
maxflow=0;
}
void add_edge(int u,int v,int c,int cost)
{
E[top].v=v;
E[top].cap=c;
E[top].flow=0;
E[top].cost=cost;
E[top].next=first[u];
first[u]=top++;
}
void add(int u,int v,int c,int cost)
{
add_edge(u,v,c,cost);
add_edge(v,u,0,-cost);
}
bool spfa(int s,int t,int n)
{
int i,u,v;
queue<int>q;
mem(vis,false);
mem(pre,-1);
for(int i=1; i<=n; i++) dis[i]=inf;
vis[s]=true;
dis[s]=0;
q.push(s);
while(!q.empty())
{
u=q.front();
q.pop();
vis[u]=false;
for(int i=first[u]; i!=-1; i=E[i].next)
{
v=E[i].v;
if(E[i].cap>E[i].flow&&dis[v]>dis[u]+E[i].cost)
{
dis[v]=dis[u]+E[i].cost;
pre[v]=i;
if(!vis[v])
{
q.push(v);
vis[v]=true;
}
}
}
}
if(dis[t]==inf)
return false;
return true;
}
int MCMF(int s,int t,int n)//minCostMaxFlow
{
int d;
int i,mincost=0;//maxflow当前最大流量,mincost当前最小费用
while(spfa(s,t,n))//表示找到了从s到t的最小费用路
{
d=inf;
for(int i=pre[t]; i!=-1; i=pre[E[i^1].v]) //遍历反向边
d=min(d,E[i].cap-E[i].flow);
maxflow+=d;//更新最大流
for(int i=pre[t]; i!=-1; i=pre[E[i^1].v]) //增广路上正向边流量+d,反向边流量-d
{
E[i].flow+=d;
E[i^1].flow-=d;
}
mincost+=dis[t]*d;//dis[t]为该路径上单位流量费用之和
}
return mincost;
}
int main()
{
int n,m,st,ed;
int u,v,w,c;
scanf("%d%d%d%d",&n,&m,&st,&ed);
init();
for(int i=1; i<=m; i++)
{
scanf("%d%d%d%d",&u,&v,&w,&c);
add(u,v,w,c);
}
int mincost=MCMF(st,ed,n);
printf("%d %d\n",maxflow,mincost);
return 0;
}
主要是理解一些概念