引入
使得整个网络的流量最大的流函数叫做最大流。举个栗子,如图 (太丑了太丑了)
边权表示容量。
我们可以观察到:
如果拆开了来看,
4-5-3-7这条增广路最大流量是3,超过了相当于水管就爆了。
2-5-3-7这条增广路最大流量是2,
2-10这条增广路最大流量是2,
如果合并起来看的话,
发现4-5-3-7如果走了的话,那么最大流量是3,那么其他边的剩余流量就会减少,变成1-2-0-4,这样的话,我们发现2-5-3-7这条路就不能走了。而2-10是可以走的,变成了0-8
这样最大流就是3+2=5了。
但是如果我第一找找的是2-5-3-7呢,那么剩余的容量就是0-3-1-5了,那么4-5-3-7只能流1,2-10不能走了,流量为3。明显更少。
那么这样要怎么办呢?回溯吗?不,我们更优美的方法解决。
我们对于每一条边建上反边,初始边权为0。
沿增广路同向边增流w[i]-=flow,反向边减流w[i^1]+=flow。
注意这里使用了’ ^ '异或符,我们需要在建邻接表时将下标从奇数开始tot=1或-1。
EK
首先介绍EK的思路,每次bfs找增广路,直至找不到了为止,时间复杂度O(nm2)。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=405,M=1e4+5;
int n,m,s,t;
bool vis[N];
int first[N],nex[M],to[M],w[M],tot=1;
struct node
{
int v,e;//存该点上一个点和边
}pre[N];
void add(int x,int y,int z)
{
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
bool bfs()
{
queue<int>a;//每次找之前初始化
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
vis[s]=1;
a.push(s);
while(!a.empty())
{
int x=a.front();
a.pop();
for(int i=first[x];i;i=nex[i])
{
int y=to[i];
if(vis[y]==0 && w[i]!=0)//如果没有被搜过,且剩余容量不为0
{
pre[y].v=x;//y前面是x
pre[y].e=i;//y与x之间的边是i
if(y==t) return 1;
vis[y]=1;
a.push(y);
}
}
}
return 0;
}
int EK()
{
int ans=0;
while(bfs())//如果存在增广路
{
int Min=0x3f3f3f3f;
for(int i=t;i!=s;i=pre[i].v)//找到最小的值,即是最大流量
Min=min(Min,w[pre[i].e]);
for(int i=t;i!=s;i=pre[i].v)
{
w[pre[i].e]-=Min; //增流
w[pre[i].e^1]+=Min;//减流
}
ans+=Min;//累计答案
}
return ans;
}
main()
{
scanf("%lld%lld",&m,&n);
s=1;t=n;
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
cout<<EK();
return 0;
}
dinic
这里主要介绍dinic算法:
dinic思路是:首先bfs分层,分好层后不断dfs找增广路,并累计答案。时间复杂度O(n2m)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5,M=5e6+5,inf=1<<29;
int first[N],nex[M],to[M],w[M],tot=1;
int n,m,s,t,ans,dep[N];
queue<int>a;
void add(int x,int y,int z)
{
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
bool bfs()
{
memset(dep,0,sizeof(dep));//初始化
while(!a.empty()) a.pop();
a.push(s);dep[s]=1;
while(!a.empty())
{
int x=a.front();a.pop();
for(int i=first[x];i;i=nex[i])
{
int y=to[i];
if(w[i] && dep[y]==0)
{
a.push(y);
dep[y]=dep[x]+1;
if(y==t) return 1;
}
}
}
return 0;
}
int dfs(int x,int flow)//flow代表该增广路的最大流量
{
if(x==t) return flow;
int rest=flow,k,i;
for(i=first[x];i&&rest;i=nex[i])
{
int y=to[i];
if(w[i] && dep[y]==dep[x]+1)
{
k=dfs(y,min(rest,w[i]));//继续找,并返回最大值
if(!k) dep[y]=0;//这条边没有剩余了,就直接去掉
w[i]-=k;
w[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,0);//反边边权为0
}
int flow=0;
while(bfs())//分层
while(flow=dfs(s,inf)) ans+=flow;//不断dfs
//这里的顺序是先给flow赋值,再判断是否是0或其他数
cout<<ans;
return 0;
}
当前弧优化
我们仅需要一个now[M]数组来记下边,下次枚举直接从这个边开始,但就这一点可以节省大量的时间。
代码实现:和上面的大体相似,只需要增加几行。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5,M=5e6+5,inf=1<<29;
int first[N],nex[M],to[M],w[M],tot=1;
int now[M],n,m,s,t,ans,dep[N];
queue<int>a;
void add(int x,int y,int z)
{
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!a.empty()) a.pop();
a.push(s);dep[s]=1;now[s]=first[s];//初始化
while(!a.empty())
{
int x=a.front();a.pop();
for(int i=first[x];i;i=nex[i])
{
int y=to[i];
if(w[i] && dep[y]==0)
{
a.push(y);
now[y]=first[y];//初始化
dep[y]=dep[x]+1;
if(y==t) return 1;
}
}
}
return 0;
}
int dfs(int x,int flow)
{
if(x==t) return flow;
int rest=flow,k,i;
for(i=now[x];i&&rest;i=nex[i])
{
int y=to[i];
if(w[i] && dep[y]==dep[x]+1)
{
k=dfs(y,min(rest,w[i]));
if(!k) dep[y]=0;
w[i]-=k;
w[i^1]+=k;
rest-=k;
}
}
now[x]=i;//记下结束的边,下次直接搜
return flow-rest;
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
int flow=0;
while(bfs())
while(flow=dfs(s,inf)) ans+=flow;
cout<<ans;
return 0;
}
计算最大流的基本dinic算法就介绍完了。qwq