下面介绍两种时间复杂度较为优秀的网络流算法:dinic和SAP。
dinic
dinic主要是以下两大优化:
-
分层
-
当前弧优化
分层
每一次dfs之前我们都从源点bfs一次,bfs只走还剩流量可流的边,通过bfs我们把残量网络分层。设i的深度为deep[i],那么在dfs是我们要保证deep[y]=deep[x]+1才走x-y这条边。
当然我们也可以多次dfs再一次bfs,这也不影响正确性。
当前弧优化
这个其实就是记录一下每一个点当前增广到那一条边了,下一次直接从这条边开始增广即可。注意每次改变deep时都要把所有的当前弧清零,因为可用的边发生了变化。
这个优化看似没有多大用,但是实际上可以大大提高效率。
SAP
sap其实就是dinic的进化版。在dinic时我们需要多次bfs来求deep,而在SAP时我们不妨在dfs中实时更新deep。
SAP的核心思路是:当一个点无法增广时,我们要更新它的deep使得它又有可能增广。
往上说的操作是把deep改为连出去的儿子的deep的min-1,但是我身边的同学打的都是把deep全部初始赋为0,然后每次+1(或者把deep全部赋为n,然后每次-1)。
当一种deep的个数变为0的时候,这就标志着增广完毕了。因为如果一种deep的个数变为0,那么就说明deep图出现了断层。在这种情况下是不可能增广成功的。
最后要注意的是,每次修改一个一个点的deep时,都要把它的当前弧优化的数组清零。
下面贴一下代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 1010
#define MAXM 1000010
struct map
{
int x;
int y;
int l;
};
map way[MAXM];
int first[MAXN],nxt[MAXM],num[MAXN],deep[MAXN],cur[MAXN],pre[MAXN],n,m,ans,tf=1,w;
int dfs(int z)
{
int i;
if(tf==0)return 0;
if(z==n)
{
w=-1;
i=pre[z];
while(i>=1&&i<=m)
{
if(way[i].l<w||w==-1)w=way[i].l;
i=pre[way[i].x];
}
ans+=w;
i=pre[z];
while(i>=1&&i<=m)
{
way[i].l-=w;
if(i<=m/2)way[i+m/2].l+=w;
else way[i-m/2].l+=w;
i=pre[way[i].x];
}
return 100;
}
else
{
while(cur[z]>=1&&cur[z]<=m)
{
i=cur[z];
if(way[i].l>0&&deep[way[i].y]==deep[z]+1)
{
pre[way[i].y]=i;
if(dfs(way[i].y)==100)return 100;
}
cur[z]=nxt[cur[z]];
}
num[deep[z]]--;if(num[deep[z]]==0){tf=0;}
deep[z]--;num[deep[z]]++;
cur[z]=first[z];
}
return 50;
}
int main()
{
int i,j;
scanf("%d %d",&m,&n);
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&way[i].x,&way[i].y,&way[i].l);
way[i+m].x=way[i].y;way[i+m].y=way[i].x;way[i+m].l=0;
}
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
for(i=1;i<=n;i++)deep[i]=n;
for(i=1;i<=n;i++)num[deep[i]]++;
for(i=1;i<=n;i++)cur[i]=first[i];
while(true)
{
dfs(1);
if(tf==0)break;
}
printf("%d",ans);
}