费用流

1. 最小费用最大流Minimum Cost Maximum Flow

  • 基本概念:网络上的每条边,除了容量外,还有一个属性:单位费用。一条边上的费用 = 流量 × 单位费用
  • 解决思路:我们已经知道,只要建了反向边,无论增广的顺序是怎样的,都能求出最大流。所以我们只需要每次都增广费用最少的一条路径即可。具体而言,将EK算法中的BFS换成SPFA即可;
阅读全文 »

EK求最大流

1. 最大流知识点梳理

  • 基本概念
    • 流网络,不考虑反向边
    • 可行流,不考虑反向边
      • 两个条件:容量限制、流量守恒
      • 可行流的流量=从源点流出的流量-流入源点的流量
      • 最大流是指最大可行流
    • 残留网络,考虑反向边,残留网络的可行流$f^{‘}$ + 原图的可行流$f$ = 原图的另一个可行流
      • |f' + f| = |f'| + |f|
      • |f'| 可能是负数
    • 增广路径
      • 割的定义
      • 割的容量,不考虑反向边,最小割是指容量最小的割
      • 割的流量,考虑反向边,f(S, T) <= c(S, T)
      • 对于任意可行流$f$,任意割[S, T],有|f| = f(S, T) <= c(S, T)
      • 最大流最小割定理
        • 可行流$f$是最大流;
        • 可行流$f$的残留网络中不存在增广路;
        • 存在某个割[S, T],有|f| = c(S, T)
    • 算法【网络流的时间复杂度just a joke】
      • EK算法 $O(nm^2)$
      • Dinic算法 $O(n^2m)$
    • 应用
      • 二分图:二分图匹配、二分图多重匹配
      • 上下界网络流:无源汇上下界可行流、有源汇上下界最大流、有源汇上下界最小流
      • 多元汇最大流

2. EK算法具体实现

思路:首先存下图,建立反向边,构建残余网络,在残余网络中不断寻找增广路径,维护残余网络,将其累加至答案中,知道找不到增广路径。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<bits/stdc++.h>
using namespace std;

const int N = 1010, M = 20010, INF = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; // f[i]代表编号为i的边的capacity
int q[N], d[N], pre[N]; // 当前边的编号、走到这条边的路径中最小的权值(最大的可行流量)、当前边的前驱路径
bool st[N]; // 标记bfs过程中已经达到了的点,防止重复搜索


void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++; // 构建正向边
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++; // 构建反向边
}

bool bfs(){
int hh = 0, tt = 0;

memset(st, false, sizeof st);

q[0] = S, st[S] = true, d[S] = INF; // 添加编号为1的边,终点为S

while(hh <= tt){
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if(!st[ver] && f[i]){ // 如果当前边的终点还没有到达过,且当前这条边的权值大于0
st[ver] = true;
d[ver] = min(d[t], f[i]);
pre[ver] = i;
if(ver == T) return true;
q[++tt] = ver;
}
}
}
return false;
}

int EK(){
int r = 0;
while(bfs()){ // 若当前残留网络中还可以找到增广路径,则累加到r中,并修改这条路径中正向边和反向边的权值
r += d[T];
for(int i = T; i != S; i = e[pre[i] ^ 1]){ // 回溯
f[pre[i]] -= d[T];
f[pre[i] ^ 1] += d[T];
}
}
return r;
}

int main(){
cin>>n>>m>>S>>T;
memset(h, -1, sizeof h);
while (m --){
int a, b, c; cin>>a>>b>>c;
add(a, b, c);
}

cout<<EK()<<endl;
return 0;
}

3. Dinic算法具体实现

概念:在稀疏图上和EK算法效果差不多,但是在稠密图上优势明显。

思路:EK算法在寻找增广路径时候是一条一条找的,显然比较低效。因此Dinic实现了多路增广;

  • 多路增广:在使用EK算法时,由于我们使用bfs找增广路,当每次贪心取得一条增广路并增广后,我们从汇点沿着增广路往前走,很可能会遇见一些点实际经过的流量小于该点所有入度的容量和,或者说在残量网络中,该点的入度和出度均大于0。我们可以利用该店后向弧的残量向该点前向弧增广。由于涉及回溯,bfs不适用;
  • 当前弧优化:对于增广操作,对于任意节点,我们每增广它的一条前向弧,意味着这条弧后所有的边都被我们多路增广过了,当我们再次处理该节点时,可以不用考虑这条弧。或者说,如果一条边已经被增广过,那么它就没有可能被增广第二次。那么我们下一次进行增广时,可以不走那些已经被增广过的边。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>

using namespace std;

const int N = 10010, M = 200010, INF = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];

void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while(hh <= tt){
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i]){
int ver = e[i]; // 当前弧优化
if(d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if(ver == T) return true;
q[++tt] = ver;
}
}
}
return false;
}

int find(int u, int limit){
if(u == T) return limit;
int flow = 0;
for(int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if(d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if(!t) d[ver] = -1;
f[i] -= t;f[i ^ 1] += t, flow += t;
}
}
return flow;
}

int dinic(){
int r = 0, flow;
while(bfs()) while(flow = find(S, INF)) r += flow;
return r;
}

int main(){
cin>>n>>m>>S>>T;
memset(h, -1, sizeof h);
while(m--){
int a, b, c; cin>>a>>b>>c;
add(a, b, c);
}

cout<<dinic()<<endl;
return 0;
}

网络流24题

0. 概述

问题编号 问题名称 问题模型 转化模型
1 飞行员配对方案问题 二分图最大匹配 网络最大流
2 太空飞行计划问题 最大权闭合图 网络最小割
3 最小路径覆盖问题 有向无环图最小路径覆盖 网络最大流
4 魔术球问题 有向无环图最小路径覆盖 网络最大流
5 圆桌问题 二分图多重匹配 网络最大流
6 最长递增子序列问题 最多不相交路径 网络最大流
7 试题库问题 二分图多重匹配 网络最大流
8 机器人路径规划问题 最小费用最大流
9 方格取数问题 二分图点权最大独立集 网络最小割
10 餐巾计划问题 线性规划网络优化 最小费用最大流
11 航空路线问题 最长不相交路径 最小费用最大流
12 软件补丁问题 最小转移代价 最短路径
13 星际转移问题 网路判定 网络最大流
14 孤岛营救问题 分层图最短路径 最短路径
15 汽车加油形式问题 分层图最短路径 最短路径
16 数字梯形问题 最大权不相交路径 最小费用最大流
17 运输问题 网络费用流量 最小费用最大流
18 分配问题 二分图最佳匹配 最小费用最大流
19 负载平衡问题 最小代价供求 最小费用最大流
20 深海机器人问题 线性规划网络优化 最小费用最大流
21 最长k可重区间集问题 最大权不相交路径 最小费用最大流
22 最长k可重线段集问题 最大权不相交路径 最小费用最大流
23 火星探险问题 线性规划网络优化 最小费用最大流
24 骑士共存问题 二分图最大独立集 网络最小割
2022不摆烂: )

学习笔记:《算法导论》第26章-最大流

0. 背景

  • 可以将有向图理解成一个”流网络“,用来回答有关物流方面的问题。
  • 设想某物质从产生它的源点经过一个系统,流向消耗该物资的过程。源点以固定速度产生该物质,而汇点则用同样的速度消耗该物质。从直观上看,系统中任何一点的物质的”流“为该物质在系统中运行的速度;
  • 应用场景:模型化流经管道的液体、通过装配线的部件、电网中的电流或通讯网络传送的信息等;
阅读全文 »

学习笔记:《算法导论》第25章-每对顶点间的最短距离

0. 提出背景

  • 问题:对一张公路图,需要制表说明每对城市间的距离。
  • 概述:
    • 基于矩阵乘法的动态规划算法,求解每对顶点间的最短路径问题【重复平方】;
    • 动态规划算法Floyd-Warshall算法;
    • 有向图传递闭包问题【与每对顶点间最短路径有关系】;
    • Johnson算法【采用图的邻接表表示法】;
阅读全文 »

组合数学概述

1. 概述

组合数学,也叫离散优化,是运筹优化的重要组成部分。

组合优化是要从呈组合数复杂度爆炸式增长的解空间中,寻找最优的解向量,即指定最优决策方案。

组合优化问题涉及了分配、调度、指挥、路由等众多类型的问题,并且她与统计学习存在密切关系。

  • 统计学习更侧重于预测和单步决策,比如:

    • 预测出了某件商品的销量,就可以知道需要进多少货;
    • 预测出了某个区域的人流量,就可以知道需要分配多少保安巡逻;
    • 检测出患者患有某种疾病,就可以知道开什么药。
  • 组合优化更侧重多方的、全局的、系统性的序列决策。同时,离散优化与连续优化在思想上有很多相通之处。

阅读全文 »

如何理解0-1背包是NPC问题?

1. 分析

  • 在0-1背包问题中,我们需要2个输入(一个数组和一个整数)来解决这个问题。

    • n个项目的数组$\{n_1, n_2, …\}$,每个项目都有它的价值指数和权重指数;
    • 整数W作为最大可接受的重量。
  • 假设n=10, W=8,则:

    n = [n1, n2, ..., n10], W=1000,因此时间复杂度为T(n)= O(nW)= O(80)

  • 若将n的大小加倍:T(n)= O(nW)= O(20 * 8)= O(160);

  • 若将W的大小加倍:并不意味着W = 20,而是长度是两倍,即W = 10000000,因此:T(n)= O(nW)= 0(10 * 128)= 0(1280)
  • 因此所需时间以指数增加,这是一个NPC问题
  • 因此,它是一个伪多项式。

Primal-Dual原始-对偶问题

1. 概述

  • Prime-Dual的意义:可以给很多组合优化问题,尤其是网络设计问题提供近似算法。
  • 应用:哈密顿回路、最大匹配的经典解法Blossom Algorithm;
阅读全文 »

Pareto Optimality 帕累托最优

帕累托最优,是经济学中的重要概念,并且在博弈论、工程学和社会科学中有着广泛的应用。

阅读全文 »

Weighted Set Cover (WSC):带权集合覆盖问题

Set Cover Problem

研究这个问题,是因为看到论文中提及了Weighted Set Cover (WSC),并论证该问题是NP-hard的。因此我希望了解什么是Weighted Set Cover。为了说明这个问题,我们首先需要知道集合覆盖(Set Cover)顶点覆盖(Vertex Cover)的区别。

阅读全文 »