快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:10关注:17

2024-04-24 13:12阅读: 41评论: 0推荐: 0

网络流小记

I.基本定义:

  • 网络:一张有向图。

  • 流量:经过一条边的流的大小,一条边 (u,v) 的流量记为 flow(u,v), 一个网络的流量定义为 f(s,x)

  • 容量:一条边的流量上限,一条边 (u,v) 的容量记为 cap(u,v)

  • 费用:经过一条边单位流量的所需费用,一条边 (u,v) 的费用记为 cost(u,v)

  • 源点:所有流的起始点, 记为 s

  • 汇点:所有流的终止点,记为 t

  • 割:是网络的一个划分, 一个割 {S,T} 的容量定义为 ||S,T||=uSvTcap(u,v)

  • 残量网络:每条边剩余可走的流量组成的网络。

网络流的性质:

  1. 斜对称性:flow(u,v)=flow(v,u)

  2. 流量守恒:ux,tyflow(u,x)=flow(y,u)

  3. 容量限制: flow(u,v)cap(u,v)

II.一些定理:

增广路定理

  • 增广路:在残量网络中的一条 st 的路径,满足路径上的残量均大于 0

一个流为最大流当且仅当网络中没有增广路,证明显然。

最大流最小割定理:

对于任意网络,最大流 f 和最小割 {S,T} 总是满足 |f|=||S,T||

  • 证明:首先显然有 |f|||S,T||,因为根据割的定义 S,T 互不相交,S>T 的流量一定大于等于 |f|,考虑如何构造取到等号。

III.最大流:

EK 算法:

对于求一张图的最大流,我们考虑引入反向边的概念。

  • 反向边:一条边的反向边定义为一条流向与原边相反,容量为 0,流量与原边相反的边。

经过反向边相当于做退流操作,类似于反悔,这也是 EK 算法的关键之处。具体的,我们将反向边一并加入到残量网络中,并与原边一起考虑。

由上面的定理,我们可以得到以下算法:

  1. 在残量网络上找一条增广路,将这条路径上的流大小记为 flow

  2. 将路径上的所有边加上 flow,反向边减掉 flow

我们暴力的使用 dfs 去找,时间复杂度 O(nm2)

Dinic 算法:

考虑优化上面的算法,EK 算法一次只找到一条增广路,这使得有一些无用的操作,考虑一次进行多路增广,即一次找到多条增广路。

我们考虑对残量网络进行分层,这样我们可以一次找到多条长度相同的增广路并进行增广。分层容易用 BFS 实现。

接着我们考虑进行多路增广,这个使用 DFS 也不难实现。这里有一个叫做当前弧优化的东西,作用是让已经考虑过的边不再考虑,可以保证时间复杂度在 O(n2m),实现开一个数组记录即可。建议结合代码食用。

qwq

一些例题:

this

(有待修缮)

IV.费用流:

在费用流问题中,每条边多了一个元素称作费用,一个流的费用等于该流经过的边的费用和乘上流的大小。费用流问题中最经典的是最小费用最大流问题,即在最大化流大小的情况下最小化费用和。

其实这个问题非常简单,我们每次选择流最大的增广路中最小费用的进行增广即可。

qwq
namespace Dinic{
struct edge{
int v, flow, cap, cost, next;
}edges[M << 1];
int head[N], idx = 1;
int cur[N], dis[N], s, t, siz, maxflow, mincost;
bool vis[N];
void add_edge(int u, int v, int cap, int cost){
edges[++idx] = {v, 0, cap, cost, head[u]};
head[u] = idx;
}
void addline(int u, int v, int cap, int cost){add_edge(u, v, cap, cost); add_edge(v, u, 0, -cost);}
bool SPFA(){
for(int i = 1; i <= siz; i++) dis[i] = INF, cur[i] = head[i], vis[i] = false;
dis[s] = 0; queue<int> Q; Q.push(s);
while(!Q.empty()){
int u = Q.front(); Q.pop();
vis[u] = false;
for(int i = head[u]; i; i = edges[i].next){
int v = edges[i].v;
if(dis[v] > dis[u] + edges[i].cost && edges[i].cap > edges[i].flow){
dis[v] = dis[u] + edges[i].cost;
if(!vis[v]) Q.push(v), vis[v] = true;
}
}
}
for(int i = 1; i <= siz; i++) vis[i] = false;
return (dis[t] != INF);
}
int dfs(int u, int flow, int &cost){
if((!flow) || u == t) return flow;
int ret = 0; vis[u] = true;
for(int& i = cur[u]; i; i = edges[i].next){
int v = edges[i].v, d;
if((!vis[v]) && dis[v] == dis[u] + edges[i].cost && (d = dfs(v, min(flow - ret, edges[i].cap - edges[i].flow), cost))){
ret += d; edges[i].flow += d; edges[i ^ 1].flow -= d; cost += edges[i].cost * d;
if(flow == ret) return flow;
}
}
vis[u] = false;
return ret;
}
void dinic(){
while(SPFA()){
int cost = 0;
maxflow += dfs(s, INF, cost); mincost += cost;
}
}
}

V.上下界网络流:

现在我们在普通网络流的基础上再给每条边增加一个属性:流量上界,即经过一条边的流量至少要大于等于流量下界。

1.无源汇上下界可行流:

对于一张

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18155070

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(41)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起