网络流总结

来源:互联网 发布:淘宝零食店 知乎 编辑:程序博客网 时间:2024/05/19 19:57

简要总结以下网络流的学习.

参考资料

  • 《挑战程序设计竞赛》

  • 《算法竞赛入门经典》-刘汝佳

  • 《算法导论》

最大流

符号说明

  • f(u,v) u, v的流量
  • c(u,v) u, v 的容量
  • s,t 源点和汇点

流网络

一个流网络总是满住以下限制条件的


流量限制: 对于任意的节点 u,v 满足 0f(u,v)c(u,v)

流量守恒: 任意顶点 uV{s,t} 满足 流入总流量等于流出总流量

在最大流问题中我们要找到从源点到汇点的最大流量

反向边

反向边可以这样理解,从 ab,发送 10 箱货物,从 ba 发送 5 箱货物,就等价于ab 发送了 5 箱货物.

残留网络(residual network)

从直观上看,给定流网络G ,与流量 f,残存网络 Gf 表示那些每条边还能调整的容量,例如,在下图中
这里写图片描述

v3v2的流量为 4容量为 9 在残存网络中,v3v2 的最大改进量为 5,即最多还能流 5 过去,而因为 v3 流了 4v2 所以 v2 流入 v3 的最多为 4,(把4流量退回去),形式化定义

cf(u,v)=c(u,v)f(u,v)f(u,v)0 (u,v)E (v,u)E() 

cf(u,v)=0的边不再算入,残存网络中.

Ford-Fulkerson方法

这是一类算法,而不是一个算法。简单的说就是
1. 初始化flow = 0;
2. while 存在一条增广路 p (从汇点到源点的道路)在残留网络中
3. 沿着增广路增广.

用朴素的深搜找增广路的方法的复杂度是O(FVE),我们可以做一些优化,先用广搜找出最短路,然后每次沿着最短路增广.这就是 Dinic 算法,

Dinic

struct Edge{  int from,to,cap;  Edge(int u,int v,int c = 0):from(u),to(v),cap(c){};};//残量网络void add_edge(int u,int v,int cap){  E.push_back(Egde(u,v,cap));G[u].push_back(E.size()-1);  E.push_back(Edge(v,u,0));  G[v].push_back(E.size()-1);}struct Dinic{  std::vector<Edge> E;  std::vector<int> G[MAX_V];  int level[MAX_V],cur[MAX_V];//分层,当前弧;  void bfs(int s){    memset(level,-1,sizeof(level));    queue<int> Q;Q.push(s);    level[s] = 0;    while (!Q.empty()) {      int u = Q.front();Q.pop();      for(int i=0 ; i<G[u].size() ; ++i){        Edge & e = E[G[u][i]];        if(e.cap>0 && level[e.to]<0){          level[e.to] = level[u]+1;          Q.push(e.to);        }      }    }  }  int dfs(int v,int t,int f){    if(v==t || f == 0)return f;    for(int& i = cur[v] ; i<G[v].size() ; ++i){      Edge & e = E[G[v][i]];Edge & rev = E[G[v][i]^1];      if(e.cap>0 && level[v]<level[e.to]){        int a = dfs(e.to,t,min(f,e.cap));        if(a>0){          e.cap-=a;          rev.cap+=a;          return a;        }      }    }    return 0;  }  int max_flow(int s,int t){    int flow = 0;    for(;;){      bfs(s);      if(level[t]<0)break;      memset(cur,0,sizeof(cur));      int f;      while ((f = dfs(s,t,INF))>0) {        flow+=f;      }    }    return flow;  }}

对于上面的建边的方式,e的反向边就是e^1,这个可以自行枚举证明.

最大流最小切割定理

以下三个条件等价:
1. fG 的最大流.
2. 残存网络中没有增广路
3. |f|=c(S,T) 其中(S,T)是最小切割

切割

G的某个切割 {S,T} 是指,将图分为两个不相交的集合,{S,T} 的容量为,从 S d到 T 的最大容量,即割边的最大容量

最小割计算

计算网络中的两个割集
{S,T}
1.求最大流
2.从s开始dfs搜索.

这是由最大流最小割定理直接得出的,
Prof
这里写图片描述

二分图匹配

二分图的一个匹配是指二分图中的一些没有公共顶点的边集,匹配数就是边集的数目,最大匹配是指,使得这样的边集的数目最大.

算法

当作网络流来处理,将UV 的边改成从 UV 的一条边,其容量为一.对于这个多元多汇问题,我们只需要连一个超级节点 S 到每一个源点,容量为1,再从每一个汇点连到一个超级汇点 容量也为一,这样这个图的最大流就是最大匹配数.

不过由于是二分图我们可不必真的这样实现

Code

int V;std::vector<int> G[MAX_V];bool used[MAX_V];int match[MAX_V];bool dfs(int u){  used[u] = true;  for(int i=0 ; i<G[u].size() ; ++i){    int v = G[u][i];int w = match[v];    if(w<0 || !used[w] && dfs(w)){      match[u] = v;match[v] = u;      return true;    }  }  return false;}int bipartite_match(){  int res = 0;  memset(match,-1,sizeof(match));  for(int i = 1 ; i<=V ; ++i){    if(match[i]<0){      memset(used,false,sizeof(used));      if(dfs(i))res++;    }  }  return res;}void add_edge(int u,int v){  G[u].push_back(v);  G[v].push_back(u);}

最小费用流

在概念上最小费用流只是在最大流的边上在附加一个费用,即求出从源点到汇点的给定流量的最小费用.

算法

先从源点找一条到汇点的最短路,然后沿着最短路增广.建图的时候将反向边的费用设为-cost.(退流退费用)

Code

struct Edge{  int from,to,cap,cost;  Edge(int f,int t,int c,int co):from(f),to(t),cap(c),cost(co){}};std::vector<Edge> E;std::vector<int> G[MAX_V];void add_edge(int u,int v,int cap,int cost){  E.push_back(Edge(u,v,cap,cost));G[u].push_back(E.size()-1);  E.push_back(Edge(v,u,0,-cost)) ;G[v].push_back(E.size()-1);}struct MCMF{  int V;  int dist[MAX_V];  int pre_E[MAX_V];//最短路径弧  bool inq[MAX_V];//spfa判断  //未判断负圈  void spfa(int s){    memset(dist,INF,sizeof(dist));    memset(inq,false,sizeof(inq));    queue<int> Q;    Q.push(s);    dist[s] = 0;    inq[s] = true;    pre_E[s] = -1;    while(!Q.empty())    {      int u = Q.front();Q.pop();      inq[u] = false;      for(int i=0 ; i<G[u].size() ; ++i){        Edge &e = E[G[u][i]];        if(e.cap>0&&dist[e.to]>dist[u]+e.cost){          dist[e.to] = dist[u]+e.cost;          pre_E[e.to] = G[u][i];          if(!inq[e.to]){Q.push(e.to);inq[e.to] = true;}        }      }    }  }  int min_cost_flow(int s,int t,int f){    int res = 0;    while (f>0) {      spfa(s);      if(dist[t]==INF)return break;//不能增广      //沿着最短路增广      int d = f;      for(int i = pre_E[t] ; i!=-1 ;i = pre_E[E[i].from])d = min(d,E[i].cap);      f-=d;      res+=d*dist[t];      for(int i = pre_E[t] ; i!=-1 ;i = pre_E[E[i].from]){        E[i].cap-=d;        E[i^1].cap+=d;      }    }    return res;  }};

负圈

f 是最小费用流 残留网络中没有负圈.

如果有负圈,很显然,沿着负圈走费用会更少,而且沿着圈走是永远满足条件的

习题

以下习题代码网址
https://vjudge.net/contest/150004#overview

poj 3041 Asteroids
二分图匹配
最小顶点覆盖问题,二分图的最小顶点覆盖等于其最大匹配.行标与列标作为其顶点,小行星作为边链接.

poj 3057 Evacuation
二分图匹配
以每一个时刻对应的门与人为顶点建图,顺次建立,直到全部人匹配完,计算时间。

poj 3281 Dining
匹配问题网络流
匹配问题的网络流,不过一定要注意拆牛.一一对应。

poj 3469 Dual Core CPU
最小割

poj 2135 Farm Tour
最小费用流

poj 2175 Evacuation Plan
最小费用流,不过注意题意,并不需要求出,只需找到最优解,也就是沿着负圈增广一次即可.

poj 3686 The Windy’s
最小费用流
建图需分析仔细. 代码见上面的网址

poj 3680 Intervals

最小费用流
难在建图分析.

0 0
原创粉丝点击