网络流算法详解
what is 网络流
相信大家都听过网络流这个名字。哦?你知道网络,还知道输入输出流?呀,看来你已经会网络流了啊!
嗯,开个玩笑
网络流(network-flows)是一种图论算法,说起来可能比较抽象,不过我们可以把网络图想象成一个水管分布图,网络流就相当于水流。边就是水管,节点就是一个转换水流的地方。
就像上面的图,它其实就是网(shui)络(guan)图,当然,流入你家的可能不止一根水管,那么,如果现在让你分配水怎么流,就是你可以控制水到了某一个点后往哪根水管流,怎么使流入你家的水最多,就是最大流问题。
另外最小割是什么呢?
就是如果有恐怖分子要拆水管,那么他割宽度为5的水管就要相应耗费5单位的力,所以他想耗尽量少的力去让你家没有水,那么他最终耗费的最小的力就是最小割。
网络流的基本性质
1、流量平衡:即 出流的量=入流的量 ,这个比较好理解,就是说如果你入流了100单位水,可水管只能流出50单位水,那么那另外50单位水就爆水管了。
2、最小割=最大流:如果恐怖分子知道你怎么分配使你家的流量最大,那么他就可以把所有水流的路径中最小的割了,那其实就是最大流其中一条路径所能提供的水量。
最大流算法
dinic
在学dinic之前,希望大家先明白EK的实现思路和原理。讲dinic会在EK的基础上讲的。
其实dinic和EK差不多,只不过dinic多了个分层,这个所谓的分层是一个限制,EK在bfs不是只要有路走就增广吗?而dinic则限制只能往下一层走。
那么来讲讲dinic怎么实现吧:
首先是分层,dinic分层用bfs来分层,这个没什么好说的,就是源点是第0层,和源点有边的点是第1层,这样bfs下去分层。当然,在分层中还可以判断汇点还有没有流可以汇入,如果汇点分不了层了,就代表没有流可以流入汇点了。
然后是找最大流,dinic的找最大流是用dfs的。具体就是从源点找到满足的点就一直递归下去,直到找到汇点,返回中途的最小值,然后更新边权,和EK一样,不过dinic是递归形式,所以可以直接在dfs上更新,不需要在写个update操作。
下面看看dinic的实现:
#include <bits/stdc++.h> using namespace std; struct littlestar{ int to; int nxt; int w; }star[200020]; int head[200020],cnt=1; void add(int u,int v,int w) { star[++cnt].to=v; star[cnt].w=w; star[cnt].nxt=head[u]; head[u]=cnt; } int n,m,s,t; queue<int> q; int dep[20020]; bool bfs() { memset(dep,0,sizeof(dep)); while(q.size()) q.pop(); q.push(s); dep[s]=1; while(q.size()){ int tmp=q.front(); q.pop(); for(int i=head[tmp];i;i=star[i].nxt){ int v=star[i].to; if(!dep[v] && star[i].w){ dep[v]=dep[tmp]+1; q.push(v); if(v==t) return 1; } } } return 0; } int maxn,inf=1<<29; int dinic(int u,int flow) { if(u==t) return flow; int rest=flow; int k; for(int i=head[u];i && rest;i=star[i].nxt){ int v=star[i].to; if(star[i].w && dep[v]==dep[u]+1){ k=dinic(v,min(star[i].w,rest)); if(!k) dep[v]=0; //剪枝 star[i].w-=k; star[i^1].w+=k; rest-=k; } } return flow-rest; } int main () { scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,0); } int flow=0; while(bfs()){ while((flow=dinic(s,inf))) maxn+=flow; } cout<<maxn; }
最小费用最大流:
就是把dinic中的bfs换成spfa就可以了
#include <bits/stdc++.h> using namespace std; struct littlestar{ int to; int nxt; int flow; int w; }star[200020]; int head[200020],cnt=1; void add(int u,int v,int flow,int w) { star[++cnt].to=v; star[cnt].w=w; star[cnt].flow=flow; star[cnt].nxt=head[u]; head[u]=cnt; } int n,m,s,t; int dis[200020],vis[200020],flow[200020],pre[200020]; int pre2[200020]; queue<int> q; int SPFA(int s,int t) { memset(dis,0x7f,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(flow,0x7f,sizeof(flow)); q.push(s); dis[s]=0;vis[s]=1;pre[t]=-1; while(q.size()){ int now=q.front(); q.pop(); vis[now]=0; for(int i=head[now];i;i=star[i].nxt){ int v=star[i].to; if(star[i].flow>0&&dis[v]>dis[now]+star[i].w){ dis[v]=dis[now]+star[i].w; pre2[v]=i; pre[v]=now; flow[v]=min(flow[now],star[i].flow); if(!vis[v]){ vis[v]=1; q.push(v); } } } } return pre[t]!=-1; } int mc,mf; int main () { scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++){ int a,b,c,d; cin>>a>>b>>c>>d; add(a,b,c,d); add(b,a,0,-d); } while(SPFA(s,t)){ int now=t; mf+=flow[t]; mc+=flow[t]*dis[t]; while(now!=s){ star[pre2[now]].flow-=flow[t]; star[pre2[now]^1].flow+=flow[t]; now=pre[now]; } } cout<<mf<<" "<<mc; }