网络流初步
网络流初步
P.S.
简单的学习了一下,以后方便复习。
相关概念
- 源点:只有出边没有入边的点。
- 汇点:只有入边没有出边的点。
- 容量和流量:每条有向边上有两个量,容量和流量。从i到j的容量通常用c(i,j)表示,流量则通常是f(i,j)。
相关性质
- 容量限制:f(u,v)≤c(u,v)
- 反对称性:f(u,v) = - f(v,u)
- 流量守恒:对于不是源点也不是汇点的任意结点,流入该结点的流量和等于流出该结点的流量和。
最大流
对于一个网络,合法的流中,使每一条边的流量之和最大的流。
EK增广路算法:
该算法的核心在于:
用bfs寻找一条从源点到汇点的增广路,并记录下这条路上最小的剩余容量,作为本条路上本次通过的流量。
然后直到找不到这样的增广路时,就结束算法,此时得到最大流。
但是注意到每次寻找是随机的,这样会对后续造成影响,从而可能得不到真正的最大流。
所以需要给每个流反悔的机会,即“退流”,用于给更好的方案让路。
这可以用建立反向边来实现,当正向边经过一个流时,反向边对应的增加这个流的流量即可。
dinic算法:
该算法是基于对EK的优化,可以发现EK的每次bfs都只寻找到了一条增广路,而同时却可能遍历整个残量网络。
而dinic则是每次遍历完残量网络后,找到了多条增广路,并完成了流量的更新。
核心内容:
首先bfs在原图上建立分层图。然后沿着分层图多路增广即可。
分层图的构造:
IL int bfs() {
while(!q.empty()) q.pop();
memset(d,0,sizeof(d));
d[S]=1,q.push(S);
while(!q.empty()) {
RG int i,y,x=q.front(); q.pop();
for(i=head[x];i;i=e[i].next)
if(e[i].v&&!d[y=e[i].to]) {
d[y]=d[x]+1,q.push(y);
if(y==T) return 1;
}
}
return 0;
}
当前弧优化:
在固定的一张分层图中,到达一个点的路径是固定的。所以在这种情况下到达了一次某个点后,之前走过的这个点的出边
就没有必要再走了,所以直接记录一下这个点下一条该走的出边即可。由于它是对于一张固定的分层图才能这样,所以每
次bfs重建分层图后需要重置该数组。
int dfs(int x,int flow) {
if(x==T) return flow;
RG int i,y,k,rest=flow;
for(i=cur[x];i&&rest;i=e[i].next)
if(d[y=e[i].to]==d[x]+1&&e[i].v) {
cur[x]=i; // Here
k=dfs(y,min(rest,e[i].v));
if(!k) d[y]=0;
rest-=k,e[i].v-=k,e[i^1].v+=k;
}
return flow-rest;
}
IL void dinic() {
RG int i,j,flow,maxflow=0;
while(bfs()) {
memcpy(cur,head,sizeof(head)); //reset
while(flow=dinic(S,inf)) maxflow+=flow;
}
}
最小割
最大流最小割定理:一个网络最大流量=最小割中边的容量之和。
费用流
注意此处涉及的费用流是先最大流再最小(大)费用的情况。
一般有spfa-EK或spfa-dinic来求解。即把二者的spfa代替掉bfs即可。
比如dinic费用流的分层图构建部分:
IL int spfa() {
memset(d,0x3f,sizeof(d));
while(!q.empty()) q.pop();
d[S]=0,q.push(S);
while(!q.empty()) {
RG int i,y,x=q.front();
in[x]=0,q.pop(); //spfa 可以多次入队!
for(i=head[x];i;i=e[i].next)
if(e[i].v&&d[y=e[i].to]>d[x]+e[i].w) {
d[y]=d[x]+e[i].w;
if(!in[y]) in[y]=1,q.push(y);
}
}
return d[T]!=inf;
}
the end
这大概就是基础网络流知识了,关于上下界网络流的学习就看这里吧。