网络流是一种类比水流的解决问题的方法。网络流有三个性质:容量限制、对称性和收支平衡。
一、容量限制
对于图中的每条边(u,v),它都有一个边权c(u,v),代表这条边的最大限流量,而容量限制则是指这条边的实际流量要小于等于最大限流量也就是f(u,v)<=c(u,v).
二、对称性
在网络流的建图过程中,每一条边都需要建一条初始边权值为0的回向边,而当一条边有一定流量时,它的回向边也要改为对应的流量,也就是f(u,v)=-f(v,u)
三、收支平衡
对于既不是源点也不是汇点的任意结点,它的流入流量总和要等于它流出流量的总和。
满足这三个性质,就是一个合法的网络流。
下面就要讲一下网络流的dinic算法:
dinic算法的基本思路就是利用bfs来搜索出从源点到汇点是否有一条未满流的路径,然后在利用dfs来遍历所有未满流路径,重复以上过程直到再没有可以bfs的路径。
而它的时间复杂度是O(n^2*m),实际上,理论数值所对应的数据几乎不会出现,通常并不会像理论数值说的那么慢,只会更快。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int next[100001];
int to[100001];
int val[100001];
int head[100001];
int tot=1;
int q[100001];
int s1[100001];
int s2[100001];
int bak[100001];
int n,m;
int S,T;
int x,y;
int ans=0;
int d[100001];
void add(int x,int y,int v)
{
tot++;
next[tot]=bak[x];
bak[x]=tot;
to[tot]=y;
val[tot]=v;
tot++;
next[tot]=bak[y];
bak[y]=tot;
to[tot]=x;
val[tot]=0;
}
bool bfs(int S,int T)
{
int r=0;
int l=0;
memset(d,-1,sizeof(d));
q[r++]=T;
d[T]=2;
while(l<r)
{
int now=q[l];
for(int i=bak[now];i;i=next[i])
{
if(d[to[i]]==-1&&val[i^1]!=0)
{
d[to[i]]=d[now]+1;
q[r++]=to[i];
}
}
l++;
}
if(d[S]==-1)
{
return false;
}
else
{
return true;
}
}
int dfs(int x,int flow)
{
if(x==T)
{
return flow;
}
int now_flow;
int used=0;
for(int &i=head[x];i;i=next[i])
{
if(d[to[i]]==d[x]-1&&val[i]!=0)
{
now_flow=dfs(to[i],min(flow-used,val[i]));
val[i]-=now_flow;
val[i^1]+=now_flow;
used+=now_flow;
if(now_flow==flow)
{
return flow;
}
}
}
if(used==0)
{
d[x]=-1;
}
return used;
}
void dinic()
{
while(bfs(S,T)==true)
{
memcpy(head,bak,sizeof(bak));
ans+=dfs(S,0x3f3f3f);
}
}
int main()
{
scanf("%d%d",&m,&n);
S=n+1;
T=n+2;
while(1)
{
scanf("%d%d",&x,&y);
if(x==-1&&y==-1)
{
break;
}
add(x,y,0x3f3f3f);
}
for(int i=1;i<=n;i++)
{
if(i<=m)
{
add(S,i,1);
}
else
{
add(i,T,1);
}
}
dinic();
if(ans==0)
{
printf("No Solution!");
return 0;
}
else
{
printf("%d\n",ans);
for(int i=2;i<=tot;i+=2)
{
if(to[i]!=S&&to[i]!=T&&to[i+1]!=S&&to[i+1]!=T)
{
if(val[i+1]!=0)
{
printf("%d %d\n",to[i+1],to[i]);
}
}
}
}
return 0;
}
这里有几个优化;
1、从汇点倒着bfs,这样能更快查找到有没有连接汇点且没满流的边。
2、当dfs过程中,如果一个点不能再往下走,那么将这个点深度变为-1,下次dfs就不会再从这个点往下搜。
3、当当前流量等于最大流量时,就不用再往下dfs了,可以直接回溯。
有的题目中,图中的点也有流量限制,那么就把这个点拆开成一个入点和一个出点,所有入边指向入点,所有出边从出点出,在两点之间连一条限流的边。
这里要注意:链式前向星建边时,边的编号初始值为1,建边数组要开双倍!