new_hope网络流

网络流与费用流

——汪凌岳

内容:

  1. 流网络
  2. 最大流问题与解决思想、实现算法
  3. 习题1-2
  4. 费用流
  5. 习题4
  6. 文献参考

tips:

建议听的时候把一些关键函数名,函数之间的关系简记下来。


一、流网络

1.形式化定义:

\(G = (V,E)\) 为一个流网络,\(s\)\(t\) 分别为网络的源节点和汇点,容量函数 \(c\),流函数 \(f\),满足以下性质:

  • 容量限制:\(\forall u, v \in V, 0 \le f(u,v) \le c(u,v)\),当 \((u,v) \notin E\) 时,\(f(u,v) = 0\)
  • 流量守恒:\(\forall u \in V - \{ s, t\}, \sum_{v \in V} f(v,u) = \sum_{v \in V} f(u,v)\)

wuhupai注:前一个\(\sum\)的意思就是所有流向u的东西,后一个\(\sum\)说的是从u流出去的所有东西,这显然是对的

值得注意的是,若流网络的边集 \(E\) 包含一条边 \((u,v)\),则不存在反方向的边 \((v,u)\),所以对于流量守恒这一性质的理解应是,对于一个点 \(u\),流入量等于流出量,那么对于整张网络而言,便有全局流入量等于全局流出量,而并非正反两条边流量相等。(形式化的定义容易带偏理解)

我们定义流网络 \(G\) 的一个流 \(f\) 的值 \(|f|\) 如下:

\[|f| = \sum_{u \in V}f(s,u) - \sum_{u \in V}f(u,s) \]

即流入源节点 \(s\) 的流之和 - 流出源节点 \(s\) 的流之和,通常情况下,右边的求和项为 \(0\)(没有流入 \(s\) 的流量),而这个完整的式子接下来会用到

2.两个简单的转化:

  1. 反平行边
    流网络中,若 \((u,v) \in E\),则 \((v,u) \notin E\),但实际情况中,若图同时存在 \((u, v)\)\((v,u)\),则称这两条边为反平行边,我们可以通过分裂其中一条反平行边,加入新节点,从而得到与原图等价的网络【画图说明】。(这里不能简单等价为一条容量为 \(c(u,v) - c(v,u)\) 的边)
  2. 超级源点与超级汇点
    若一张网络有多个源点与多个汇点,则可通过加入超级源点与超级汇点,设容量为正无穷,显然地,这样得到的网络与原图等价。

3.思考题

假定除边的容量外,流网络还有节点容量,记为 \(l(v)\),则如何构造除一个带有节点容量的流网络 \(G'\) 转化为一个等价的不带有节点容量的流网络 \(G\)

\({拆点,加边} 【画图说明】\)


二、最大流问题与解决思想、实现算法

1. 概念引入:

  1. 残存网络
    设残存网络 \(G_f\)\(G_f\) 由仍有空间对流量 \(f\) 进行调整的边构成。但同时,为了试探更大的流量,可能会需要有对正流量 \(f(u,v)\) 的缩减,此时则需要引入原图 \(G\) 中没有的边,利用反向流量抵消正向流量,从而达到缩减。
    因此,我们形式化地将残存网络 \(G_f\) 中的残存容量定义为:

    \[c_f(u,v) = \begin{cases} c(u,v) - f(u,v) & (u,v) \in E \\ f(v,u) & (v,u) \in E \end{cases}\]

    而根据流网络性质,\(c_f(u,v)\) 仅会取到上述的一个值。【画图说明】
    由图可以看出,残存“网络”出现了反平行边,不符合流网络的定义,但除此以外都符合流网络的性质。
    \(f'\)\(G_f\) 中的一个流,定义 \(f \uparrow f'\)\(f'\)\(f\) 的递增,形式如:

    \[(f \uparrow f')(u, v) = f(u,v) + f'(u, v) - f'(v, u),(u,v) \in E \]

    (即边 \((u,v)\) 的流量增加 \(f'(u,v)\),又减少了 \(f'(v,u)\))
    根据数学推导,可以得到 \(f \uparrow f'\)\(G\) 中的一个流(满足流量守恒和容量限制),且 \(|f \uparrow f| = |f| + |f'|\)

  2. 增广路径
    给定流网络 \(G = (V, E)\) 和流 \(f\),增广路径 \(p\) 是残存网络 \(G_f\) 中一条从源点 \(s\) 到汇点 \(t\) 的简单路径。定义在一条增广路径 \(p\) 上能够为每条边增加的流量的最大值\(p\) 的残存容量(注意与残存网络中残存容量的区分),记作:

    \[c_f(p) = \min(c_f(u,v):(u,v) \in p) \]

    可以很直观地感受到,当 \(f\) 增加了大小为 \(c_f(p)\) 的流时,\(f\) 越接近最大流。

  3. 流网络的切割
    流网络 \(G = (V,E)\) 中的一个切割 \((S,T)\) 将点集 \(V\) 划分为 \(S\)\(T = V - S\) 两个集合,使得 \(s \in S\), \(t \in T\)。定义横跨切割 \((S,T)\)净流量 \(f(S,T)\) 如下:
    wuhupai注:这其实就是在这个截面上流过去的流,这个流是流过去的减流回来的

    \[f(S,T) = \sum_{u \in S}\sum_{v \in T}f(u,v) - \sum_{u \in S}\sum_{v \in T}f(v,u) \]

    切割 \((S,T)\)容量是:

    \[c(S,T) = \sum_{u \in S}\sum_{v \in T}c(u,v) \]

    还是一样的,\((u,v) 和 (v,u)\) 只能有其中一条在 \(E\) 中,不存在的一条,其 \(|f| = 0\)
    一个网络的最小切割是整个网络中 \({容量最小}\) 的切割。
    我们可以发现,切割容量只计算了从 \(S\)\(T\) 的容量和,这与流的定义有所区别。
    wuhupai注:这就是从S流向T的流量减去从T流向S的流量
    wuhupai注:切割就是将一个图分成两半
    wuhupai注:切割的容量就是在这个横截面上的边的容量之和

2. 引理证明【重要】:

  1. 对于给定的流 \(f\),横跨任何切割的净流量都相同,都等于 \(|f|\)
    wuhupai注:我喜欢感性理解,不妨将这张图看成一个并联电路,那么支路电流之和就等于干路电流。我任选一些支路,电流之和就等于干路电流
    \(\triangle\)证明如下:
    重写流量守恒式,\(\forall u \in V - \{s, t\}\)

    \[\sum_{v \in V} f(v,u) - \sum_{v \in V} f(u,v) = 0 \]

    根据 \(|f|\) 定义,加入上式(针对 \(S - \{s\}\) 部分的求和),

    \[|f| = \sum_{v \in V}{f(s,v)} - \sum_{v \in V}{f(v,s)} + \sum_{v \in S - \{s\}} \left( \sum_{v \in V}{f(v,u)} - \sum_{v \in V}{f(u,v)} \right) \]

    展开重组得,

    \[|f| = \sum_{v \in V} \left(f(s,v)+\sum_{v \in {S - \{s\}}}f(v,u) \right ) - \sum_{v \in V}\left(f(v,s) + \sum_{v \in S - \{s\}} f(u,v) \right) \]

    惊奇地发现,

    \[|f| = \sum_{v \in V}\sum_{u \in S}f(u,v) - \sum_{v \in V}\sum_{u \in S}f(v,u) \]

    (跟 \(f(S,T)\) 已经有点像了,集合不同)
    因为 \(V = S \cup T\),且有 \(S \cap T = \varnothing\),可将针对 \(V\) 的求和,分解为针对 \(S\)\(T\) 的求和,得到

    \[|f| = \sum_{u \in S} \left( \sum_{v \in S}f(u,v) + \sum_{v \in T}f(u,v) \right) - \sum_{u \in S} \left( \sum_{v \in S}f(v,u) + \sum_{v \in T}f(v,u) \right) \]

    "观察到",

    \[|f| = \sum_{u \in S}\sum_{v \in T}f(u,v) - \sum_{u \in S}\sum_{v \in T}f(v,u) + \left( \sum_{u \in S}\sum_{v \in S}f(u,v) - \sum_{u \in S}\sum_{v \in S}f(v,u) \right ) \]

    括号内,\(u,v\) 对称,值为 \(0\)
    我们终于得到,

    \[|f| = \sum_{u \in S}\sum_{v \in T}f(u,v) - \sum_{u \in S}\sum_{v \in T}f(v,u) = f(S,T) \]

    感性地理解,根据流量守恒和流网络的连通性( \(\forall u \in V, \exist s \rightarrow u \rightarrow t\) ),无论在哪里划分,从 \(S\)\(T\) 的净流量总是恒定的,且等于 \(|f|\)

  2. 流网络 \(G\)任意\(|f|\) 不能超过 \(G\) 任意\(c(S,T)\)
    wuhupai注:数学推导较好理解,也可以感性理解
    \(\triangle\)证明如下:
    \(f\) 为任意流,\((S,T)\) 为任意切割。

    \[|f| = f(S,T) = \sum_{u \in S}\sum_{v \in T}f(u,v) - \sum_{u \in S}\sum_{v \in T}f(v,u) \]

    \[|f| \le \sum_{u \in S}\sum_{v \in T}f(u,v) \]

    \[\sum_{u \in S}\sum_{v \in T}f(u,v)-\sum_{u \in S}\sum_{v \in T}f(v,u) \le \sum_{u \in S}\sum_{v \in T}f(u,v) \]

    \[\sum_{u \in S}\sum_{v \in T}f(u,v) \le \sum_{u \in S}\sum_{v \in T}f(u,v) + \sum_{u \in S}\sum_{v \in T}f(v,u) \]

    \[\sum_{u \in S}\sum_{v \in T}f(u,v) \le \sum_{u \in S}\sum_{v \in T}c(u,v) \]

    \[|f| \le \sum_{u \in S}\sum_{v \in T}f(u,v) \le \sum_{u \in S}\sum_{v \in T}c(u,v) = c(S,T) \]

    最直观的结论是,最大流 \(\le\) 最小切割

  3. 最大流最小切割定理
    \(f\) 为流网络 \(G = (V, E)\) 中的一个流,则有以下 \(3\) 个条件等价:
    \(a\). \(f\)\(G\) 的一个最大流
    \(b\). 残存网络 \(G_f\) 不包括任何增广路径
    \(c\). \(|f| = c(S,T)\),其中 \((S,T)\)\(G\) 的某个切割
    \(\triangle\)证明如下:
    \(a \Rightarrow b\),显然
    \(b \Rightarrow c\),假定 \(G_f\) 不包含任何增广路径 \(p\),即任意的 \(c_f(p) = 0\),在这里认为是"不存在" \(s\)\(t\) 的路径。定义 \(S = \{v \in V : 在 G_f 中存在一条路径s \rightarrow v\}, T = V - S\)。显然有 \(s \in S, t \in T\),所以 \((S,T)\)\(G\) 的一个切割。
    设有 \(u \in S, v \in T\)

    1. 若有 \((u,v) \in E\),则必有 \(f(u,v) = c(u,v)\),否则根据残存网络定义,\(c_f(u,v) = c(u,v) - f(u,v) > 0\),会有 \((u,v) \in E_f\),与条件 \(b\) 矛盾。
    2. 若有 \((v,u) \in E\),则必有 \(f(v,u) = 0\),否则根据残存网络定义,\(c_f(u,v) = f(v,u) > 0\),会有 \((u,v) \in E_f\),与条件 \(b\) 矛盾。
    3. 若有 \((u,v) \notin E,(v,u) \notin E\),则 \(f(u,v) = f(v,u) = c_f(u,v) = 0\),可行。

    综上,可以得到,

    \[|f| = f(S,T) = \sum_{u \in S}\sum_{v \in T}f(u,v) - \sum_{u \in S}\sum_{v \in T}f(v,u) = \sum_{u \in S}\sum_{v \in T}c(u,v) - \sum_{u \in S}\sum_{v \in T}0 = c(S,T) \]

    此时,\(|f|\) 最大,\(c(S,T)\) 最小,即最大流 \(=\) 最小切割。

    \(c \Rightarrow a\), 由证明 \(2\)\(|f| \le c(S,T)\),则此时 \(f\) 便为最大流。
    wuhupai注:我们取出一个最大流和最小割,考虑这个最大流的残存网络,因为这是不存在增广路径,这时从S点出发能到达的集合和从T点出发能到达的点的集合就构成了一个割,所以流的集合和割的集合都有了这个元素,因为大于等于的关系,那么这个最大流就是最小割

3. Ford-Fulkerson思想:

  1. 步骤
    \(|f| = 0\) \(\rightarrow\) 构建残存网络 \(G_f\) \(\rightarrow\) 寻找增广路径 \(p\) \(\rightarrow\) \(|f| = |f| + c_f(p)\) \(\rightarrow\) 直至 \(G_f\) 不存在 \(p\) 时结束。

  2. 随堂思考
    为何最后找到的 \(|f|\) 是最大流,尝试用之前讲过的知识解释。

最大流最小切割定理的三个等价条件。

4. 多种实现方式:

本质都是 \(\texttt{Ford-Fulkerson}\) 思想,只不过实现效率不同。

wuhhupai注:new_hope几乎没讲反向边的作用,这里来讲一下

画错了,懒
这是一个我也不知道是什么的东西,但建图建出来就是这样,因该是流网络和残存网络的叠加体,边权为0的边是流网络,正的是残存网络(因为什么都没有操作)
比如说我找到了两条增广路径(只是演示)

那么我要找下一条增广路

(给节点标了个号)
此时我们发现原来1-2-3-6和1-4-3-6的增广路变成了1-4-3-6和1-2-5-6达到了找增广路的目的,所以这是对的,感性理解并且会用就行
————————————————————————————————————————————————

  1. \(\texttt{Edmonds−Karp}\) 算法
    • 主要步骤:每次 bfs 搜一遍残存网络,找到一条增广路,更新最大流与残存网络。
    • 时间复杂度:\(O(nm^2)\) (\(n\) 点数,\(m\) 边数)
  2. \(\texttt{Dinic}\) 算法
    • 主要步骤:每次 bfs 搜一遍残存网络,建分层图,找到多条可能的增广路,再 dfs 找到增广路的残存容量,更新最大流与残存网络。
    • 时间复杂度:\(O(n^2m)\) (\(n\) 点数,\(m\) 边数)
// Edmonds−Karp 核心部分
int bfs() {  
    for(int i = 1; i <= n; i ++) vis[i] = 0;
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    dis[s] = 2005020600;
    while(!q.empty()) {
        int x = q.front();
        q.pop();
        for(int i = head[x]; i; i = e[i].net) {
            if(e[i].val == 0) continue;    
            int v = e[i].to;
            if(vis[v] == 1) continue; 
            dis[v] = min(dis[x], e[i].val);
            pre[v] = i;//记录路径
            q.push(v);
            vis[v] = 1;
            if(v == t) return 1;
        }
    }
    return 0;
} //寻找增广路
void update() {   
    int x = t;
    while(x != s) {
        int v = pre[x];
        e[v].val -= dis[t];
        e[v^1].val += dis[t];
        x = e[v^1].to;
    }
    ans+=dis[t];   
} //构建残存网络
//Dinic 核心部分
bool bfs() {
    queue<int> q; memset(dep,0,sizeof(dep)); 
    q.push(s), dep[s] = 1;
    while(!q.empty()) {
        int fr = q.front(); q.pop();
        for(int i = head[fr]; i; i = edge[i].ne) {
            int ed = edge[i].v, w = edge[i].c;
            if(!dep[ed] && w) {
                dep[ed] = dep[fr] + 1;
                if(ed == t) return true;
                q.push(ed);
            }
        }
    } 
    return false;
} // 分层图找增广路
int dfs(int u,int minf) {
    if(u == t) return minf;
    int sum = 0;
    for(int i = head[u]; i; i = edge[i].ne) {
        int ed = edge[i].v, w = edge[i].c;
        if(dep[ed] == dep[u] + 1 && w) {
            int f = dfs(ed,min(minf,w));
            edge[i].c -= f;
            edge[i^1].c += f;
            minf -= f;
            sum += f;
            if(minf == 0) break;
        }
    }
    if(sum == 0) dep[u] = 0;
    return sum;
} // 构建残存网络,更新最大流

视频模拟网络流

三、习题1-2

“网络流的题目难在建模。”——Rayleigh.

1. 最大权闭合子图问题

1. 前置知识

闭合子图:对于有向图 \(G\),存在点集 \(V\),满足点集内所有的点,他们的出边指向的点都是 \(V\) 内的点,将有向边添加上,就是一个子图。(可以有入边)
wuhupai注:

简单割:在一个割中,如果连接两个集合的边都是源点或者汇点的邻边,那么这个割被称为简单割。

最大权闭合子图即求解上述子图中点权和最大的子图。

如何将闭合子图变成流网络?

2. 步骤:

建一个源点,和一个汇点;
\(s\) 连向权值为正的点,流量赋为点权;
权值为负的点向 \(t\) 连一条边,流量赋为点权的绝对值;
点与点之间的有向边不变,流量赋为正无穷。

原理?
//这个v1假设成了闭合子图
Pictures/最大权闭合子图.png

割的所有情况共有四种( \(S\) 集合到 \(T\) 集合共四种情况),其中有两种情况不存在,第①种情况不存在是因为我们在建图时就不会建 \(s\)\(t\) 的边,第②种情况不存在是根据闭合子图的定理,从 \(v_1\) 出发的点一定会回到 \(v_1\),不会到 \(v_2\)

为啥割中没有 \(s \rightarrow v_1\) 的边,假设有,则会被分为 \(\{s\}, G_1, G_2\) 三个部分,与题设不符,再要是有 \(s \rightarrow v_1\) 而没有 \(s \rightarrow v_2\) 会怎么办?答案是此时 \(S\) 中同时存在了 \(s\)\(t\),这与割的定义矛盾。

3. 数学推导:

设简单割 \((S,T)\),闭合子图 \(G_1 = (V_1, E_1)\),原图 \(G\) 的闭合子图的补图 \(G_2 = (V_2, E_2)\)

由图知,割中不存在 \(v_1 \rightarrow v_2\),和 \(s \rightarrow t\) 的边,只有 \(v_1 \rightarrow t\)\(s \rightarrow v_2\) 的边,所以可以得到该割的容量 \(c(S,T)\) 为:

\[c(S, T) = c(V_1, \{t\}) + c(V_2, \{s\}) = \textcolor{Red}{\sum_{v \in V_1^{-}} -W_v} + \sum_{v \in V_2^{+}} W_v \]

闭合子图的权值

\[W = \sum_{v \in V_1^{+}} W_v + \sum_{v \in V_1^{-}} W_v = \sum_{v \in V_1^{+}} W_v - \textcolor{Red}{\sum_{v \in V_1^{-}} -W_v} \]

两个量相加有(红色式子抵消),

\[W + c(S,T) = \sum_{v \in V_2^{+}} W_v + \sum_{v \in V_1^{+}} W_v = \sum_{v \in V^{+}} W_v \]

最后得到,

\[W = \sum_{v \in V^{+}} W_v - c(S,T) \]

在左项恒定的情况下,若要得到最大的 \(W\),则仅需求得最小的 \(c(S,T)\),而最大流最小切割定理告诉我们,这可以转化为最大流求得。

wuhupai注:这里我们求的是v1这个闭合子图的权值之和。

有啥用呢?

CF311E

为啥是网络流,怎么与最大权闭合子图建立联系?

分析题目,发现 \(0/1\) 修改比较麻烦,先假设所有的点都被改为 \(1\)。当前的答案是:所有 \(1\) 要求的 \(w\) 之和 \(-\) 所有修改为 \(0\) 点的 \(v\) 之和 \(-\) \(g\) 乘上 \(0\) 要求中要求倒给的个数。

现在,在上述的假设下,如果要满足一个 \(0\) 要求,那么该要求所有相关的点都必须进行修改,如果一个点进行了修改,那么该点所有有关的 \(1\) 要求都不满足。

这和之前 闭合子图中一个点被选择了则后继必须被选择 这一性质是一样的,我们考虑建图。

怎么建图?

注意接下来的分析还是基于所有点都被改为 \(1\) 的题设。

原先为 \(0\) 的点,因为之前已经被修改过一次,如果修改该点就相当于反悔,会产生 \(v\) 的贡献。

原先为 \(1\) 的点,如果修改该点,会产生 \(-v\) 的贡献。

对于每个 \(0\) 要求,如果将该要求改为满足,那么对答案会产生 \(w\) 贡献,如果该节点还要倒贴,一共会产生 \(w+g\) 贡献,将贡献作为点的权值,而且所有与它相关的点都必须选,于是连该要求到所有有关的点的边(出边)。

对于每个 \(1\) 要求,如果将该要求改为不满足,那么对答案会产生 \(-w\) 贡献,如果还要倒贴,一共会产生 \(-w-g\) 贡献,将贡献作为点的权值,而且所有与它相关的点一个被修改该要求也会不满足,所以连所有有关的点到该要求的边(入边)。

接着我们按照前面所讲的步骤,利用网络流求解最大权闭合子图问题。

补充:最终构造的流网络中,贡献为所有的所有 \(0\) 要求的 \(w\) 之和 \(+\) 所有 \(0\) 点的修改代价 \(+\) \(g\) 乘上 \(0\) 要求中要求倒给的个数 \(-\) 最小割,加上题设的答案,最终答案为 \(\sum w - MinCut\)

2. 图上问题

建模方式:每个点拆成入点和出点转成二分图匹配。

例:最小路径覆盖问题
给定一张图,求最少能用多少条边不相交的路径覆盖所有点。

考虑将每个点 \(u\) 拆成入点 \(in_u\) 和出点 \(out_u\),那么对于一条边 \((u,v)\),它的作用就是将 \(in_u\) 所属的路径与 \(out_u\) 所属的路径合并,使得路径数 \(-1\)

由于路径不能相交,所以每个 \(in_u\)\(out_u\) 只能合并一次,那么就相当于初始时有 \(n\) 条路径,我们希望用若干条边减去最多的路径数。
该数目就等于以 \(in_u\)\(out_u\) 为点,以 \((in_u,out_u)\) 为边的二分图最大匹配。
于是有:最小路径覆盖数 = 总点数 - 最大匹配数。

LuoGu P2764

思考:用匈牙利算法和最大流算法分别如何实现路径输出

匈牙利算法:利用数组 \(match[]\) 递归输出。

bool find(int x) {
    for(int it : g[x]) {
        if(vis[it]) continue;
        vis[it] = 1;
        if(!match[it] || find(match[it]))
            match[it] = x; return true;
    }
    return false;
}
void rec(int u) {
    r[t].push_back(u);
    for(int it : g[u])
        if(match[it] == u) rec(it);
    return;
}
void solve() {
    int ct = 0;
    for(int i = 1; i <= n; i ++) {
        memset(vis, 0, sizeof(vis));
        ct += find(i);
    }
    for(int i = 1; i <= n; i ++)
        if(!match[i]) t ++, rec(i);
    for(int i = 1; i <= t; i ++) {
        for(int it : r[i]) cout << it << " ";
        cout << "\n";
    }
    cout << n - ct;
}

最大流算法:在残存网络中寻找满流的边。

留给大家发挥

图上问题其实都与之前二分图的解法相同,不再过多介绍。

3. 最小割问题

广义地来说,最大权闭合子图问题可归类于最小割问题,但发现答案具有闭合子图的性质较难(对于讲者来说),最小割通常为“选择”问题。

一类“选择”问题:选择若干个物品使得收益之和最大(即没选的物品收益之和最小),满足约束。

建模方式:
源点向每个物品连边,边权为收益,割掉该边表示不选该物品;
每个物品向汇点连边,边权为代价,割掉该边表示选该物品;
中间的连边视题目约束而定。
最大值:\(Ans = \sum val - MinCut\)

这里不太好理解的地方在于,\(-MinCut\) 实际上是对于答案的贡献,比如一个物品 \((val_i,w_i)\),其价值 \(val\) 已被统计到 \(\sum val\) 中,所以有选该物品则需带上代价 \(w_i\),不选该物品要撤销价值 \(v_i\)

Luogu P1646

如果我们暂且不考虑第 \(3 \~\ 6\) 个矩阵,怎么做?

非常简单,对于一个人拆成两个点,\(s\) 连向 \(u_1\) 代表选理科,\(u_2\) 连向 \(t\) 代表选文科,\(u_1\) 连向 \(u_2\) ,容量正无穷,根据上述套路求解即可。

考虑如何体现 \((i,j)\) 同时/不同时选文科的贡献?

首先,如果同时选文科肯定会有新增的贡献,根据得到答案的套路,我们肯定是要新增一个节点并与 \(s\) 相连。

\((i,j)\) 不同时选文科时,假设有 \(s \rightarrow i\) 而没有 \(s \rightarrow j\),同时也有 \(j \rightarrow t\) 而没有 \(i \rightarrow t\)

\((i,j)\) 同时选文科时,有 \(s \rightarrow i\)\(s \rightarrow j\),同时没有 \(j \rightarrow t\)\(i \rightarrow t\)

充分发扬人类智慧,新增一个节点 \(p\),从 \(s\) 连一条容量为都选择文科的贡献到 \(p\),从 \(p\) 连向 \(i,j\) 的边容量为正无穷。【画图说明】(理科操作同理)

P2

4. 总结

建模还是得多练习才会有经验的,具体的其他 trick 包括但不限于:拆点限流,加入时间变量等,这些要等各位自己去发现总结了。


四、费用流

最小(大)费用最大流:在满足最大流的前提下使得所花费用最少(多)。
实现:寻找增广路时使用SPFA处理。
方式:基于 \(\texttt{EK}\)\(\texttt{Dinic}\)

Luogu P3381 【模板】最小费用最大流

板题,这里是基于 \(\texttt{EK}\) 的代码。(似乎费用流中 \(\texttt{EK}\) 用得多些)

bool spfa()
{
    clear();
    queue<int> q;
    q.push(s); vis[s] = 1; mxcf[s] = inf; dis[s] = 0;
    while(q.size())
    {
        int fr = q.front(); 
        q.pop(); vis[fr] = 0;
        for(int i = hd[fr]; i; i = e[i].nx) 
        {
            int v = e[i].v, C = e[i].C, F = e[i].F;
            if(C && dis[v] > dis[fr] + F) 
            {
                dis[v] = dis[fr] + F;
                if(!vis[v]) { q.push(v); vis[v] = 1; }
                mxcf[v] = min(mxcf[fr], C);
                pre[v] = i;
            }
        }
    }
    return mxcf[t] > 0;
}
void update()
{
    int v = t;
    do {
        int id = pre[v];
        e[id].C -= mxcf[t], e[id ^ 1].C += mxcf[t];
        v = e[id ^ 1].v;
    } while(v != s);
    mxFl += mxcf[t];
    mnFe += mxcf[t] * dis[t];
}   

LuoGu P6577 【模板】二分图最大权完美匹配

理解透后应该也可以写了。

CF277E Binary Tree on Plane

约束——二叉树的性质:“树”和“二叉”的性质。

想清楚上面这个问题大概就好做了。
树的性质:一个节点必须是一个节点的儿子。
二叉树的性质:一个节点最多有两个儿子。

建图?判断无解?
可能会有同学想到拆点限流,但这道题对于一个点有双重限制,所以这里使用拆点。
我们根据这两个约束对一个节点拆为 \(u_{fa}\)\(u_{son}\) 两个点,\(s\)\(u_{fa}\) 连容量为 \(2\) 的边,代价 \(0\),代表可最多容纳两个儿子,\(u_{son}\)\(t\) 连容量为 \(1\) 的边,代价 \(0\),代表最多成为一个节点的儿子。\(u_{fa}\)\(v_{son}\) 连容量 \(1\),代价距离的边。最终一条 \(s \rightarrow u_{fa} \rightarrow v_{son} \rightarrow t\) 的路径代表 \(u\) 成为了 \(v\) 的父亲。

\(\triangle\) 另一方面,最大流中很少出现带小数的流,所以考虑将距离作为代价。

LuoGu P4066

路径不相交问题经典思路:若有两条路径相交,则可交换两人身份,具体在这道题也有体现,\(\text{Dong Shu Cheng}\) 的一篇题解中也有涉及。(所以本质上“不相交”只是用来迷惑的)

建图?

应该能想到,对于每个点的限制拆成 (入点,出点,边) 考虑,连边细节留给大家,最后跑最大费用最大流即可。

Tips:连边需要优化。(最大流复杂度)

网络流和最大流的建模技巧非常相似,只是加上了一些限制,大家可以在练习中慢慢体会。

五、结语

希望大家听完有所收获

六、文献参考:

模型
https://www.cnblogs.com/YSFAC/p/13206167.html
https://zhuanlan.zhihu.com/p/553454908
https://blog.csdn.net/GROZAX/article/details/128512825
https://blog.csdn.net/qq_45735851/article/details/113811882
例题
洛谷
OI - wiki
基础理论
算法导论

posted @ 2024-03-09 14:14  wuhupai  阅读(25)  评论(0编辑  收藏  举报