Loading docs/ds/dsu.md +2 −4 Original line number Diff line number Diff line Loading @@ -20,9 +20,7 @@ void makeSet(int size) { ## 查找 !!! 举个例子 几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 在这样的思想下,并查集的查找算法诞生了。 Loading Loading @@ -55,7 +53,7 @@ int find(int x) { } ``` 不太懂的话我们就上两张图吧 上两张图:  Loading docs/graph/bridge.md +0 −2 Original line number Diff line number Diff line Loading @@ -46,8 +46,6 @@ low[u] = min(low[u], num[v]); [洛谷 P3388【模板】割点(割顶)](https://www.luogu.org/problemnew/show/P3388) ### Code ??? "例题代码" ```cpp /* Loading docs/graph/flow/max-flow.md +135 −139 Original line number Diff line number Diff line Loading @@ -332,11 +332,13 @@ struct ISAP { ## Push-Relabel 预流推进算法 该方法在求解过程中忽略流守恒性,并每次对一个结点更新信息,以求解最大流 该方法在求解过程中忽略流守恒性,并每次对一个结点更新信息,以求解最大流。 有 HLPP 的主流算法 ### 通用的预流推进算法 推送 - 重贴标签算法通过对单个结点的更新操作,直到没有结点需要更新来求解最大流 首先我们介绍预流推进算法的主要思想,以及一个可行的暴力实现算法。 预流推进算法通过对单个结点的更新操作,直到没有结点需要更新来求解最大流。 算法过程维护的流函数不一定保持流守恒性,对于一个结点,我们允许进入结点的流超过流出结点的流,超过的部分被称为结点 $u(u\in V-\{s,t\})$ 的 **超额流** $e(u)$ : Loading @@ -344,24 +346,24 @@ $$ e(u)=\sum_{(x,u)\in E}f(x,u)-\sum_{(u,y)\in E}f(u,y) $$ 若 $e(u)>0$ ,称结点 $u$ **溢出** . 若 $e(u)>0$ ,称结点 $u$ **溢出** 。 推送 - 重贴标签算法维护每个结点的高度 $h(u)$ ,并且规定溢出的结点 $u$ 如果要推送超额流,只能向高度小于 $u$ 的结点推送;如果 $u$ 没有相邻的高度小于 $u$ 的结点,就修改 $u$ 的高度(重贴标签)。 预流推进算法维护每个结点的高度 $h(u)$ ,并且规定溢出的结点 $u$ 如果要推送超额流,只能向高度小于 $u$ 的结点推送;如果 $u$ 没有相邻的高度小于 $u$ 的结点,就修改 $u$ 的高度(重贴标签)。 #### 高度函数 准确地说,推送 - 重贴标签维护以下的一个映射 $h:V\to \mathbf{N}$ : 准确地说,预流推进维护以下的一个映射 $h:V\to \mathbf{N}$ : - $h(s)=|V|,h(t)=0$ - $\forall (u,v)\in E_f,h(u)\leq h(v)+1$ 则称 $h$ 是残存网络 $G_f=(V_f,E_f)$ 的高度函数。 称 $h$ 是残存网络 $G_f=(V_f,E_f)$ 的高度函数。 引理 1:设 $G_f$ 上的高度函数为 $h$ ,对于任意两个结点 $u,v\in V$ ,如果 $h(u)>h(v)+1$ ,则 $(u,v)$ 不是 $G_f$ 中的边。 算法只会在 $h(u)=h(v)+1$ 的边执行推送。 #### 推送 -Push #### 推送(Push) 适用条件:结点 $u$ 溢出,且存在结点 $v((u,v)\in E_f,c(u,v)-f(u,v)>0,h(u)=h(v)+1)$ ,则 push 操作适用于 $(u,v)$ 。 Loading @@ -369,7 +371,7 @@ $$ 如果 $(u,v)$ 在推送完之后满流,将其从残存网络中删除。 #### 重贴标签 -Relabel #### 重贴标签(Relabel) 适用条件:如果结点 $u$ 溢出,且 $\forall (u,v)\in E_f,h(u)\leq h(v)$ ,则 relabel 操作适用于 $u$ 。 Loading @@ -394,11 +396,9 @@ $$ 上述将 $(s,v)\in E$ 充满流,并将 $h(s)$ 抬高,使得 $(s,v)\notin E_f$ ,因为 $h(s)>h(v)$ ,而且 $(s,v)$ 毕竟满流,没必要留在残存网络中;上述还将 $e(s)$ 初始化为 $\sum_{(s,v)\in E}f(s,v)$ 的相反数。 #### 通用执行框架 无需按照特定顺序,执行以下过程: #### 通用算法 - 只要存在结点 $u$ 满足 push 或 relabel 的条件,就执行对应的操作。 我们每次扫描整个图,只要存在结点 $u$ 满足 push 或 relabel 操作的条件,就执行对应的操作。 如图,每个结点中间表示编号,左下表示高度值 $h(u)$ ,右下表示超额流 $e(u)$ ,结点颜色的深度也表示结点的高度;边权表示 $c(u,v)-f(u,v)$ ,绿色的边表示满足 $h(u)=h(v)+1$ 的边 $(u,v)$ (即残存网络的边 $E_f$ ): Loading @@ -414,8 +414,7 @@ $$ 可以发现,最后的超额流一部分回到了 $s$ ,且除了源点汇点,其他结点都没有溢出;这时的流函数 $f$ 满足流守恒性,为最大流,即 $e(t)$ 。 #### 核心代码 ???+ "核心代码" ```cpp const int N = 1e4 + 4, M = 1e5 + 5, INF = 0x3f3f3f3f; int n, m, s, t, maxflow, tot; Loading Loading @@ -443,20 +442,18 @@ void relabel(int u) { ### HLPP 算法 最高标号预流推进算法(High Level Preflow Push)是基于推送 - 重贴标签算法的优先队列实现,该算法优先推送高度高的溢出的结点,算法算法复杂度 $O(n^2\sqrt m)$ 。 最高标号预流推进算法(High Level Preflow Push)是基于预流推进算法的优先队列实现,该算法优先推送高度高的溢出的结点,算法算法复杂度 $O(n^2\sqrt m)$ 。 具体地说,HLPP 维护以下过程: 具体地说,HLPP 算法过程如下: 1. 初始化(基于推送 - 重贴标签算法); 1. 初始化(基于预流推进算法); 2. 选择溢出结点(除 $s,t$ )中高度最高的结点 $u$ ,并对它所有可以推送的边进行推送; 3. 如果 $u$ 仍溢出,对它重贴标签,回到 2; 3. 如果 $u$ 仍溢出,对它重贴标签,回到步骤 2; 4. 如果没有溢出的结点,算法结束。 #### BFS 优化 HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可以在初始化高度的时候进行优化: 具体来说,我们初始化 $h(u)$ 为 $u$ 到 $t$ 的最短距离;特别地, $h(s)=n$ 。 HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可以在初始化高度的时候进行优化。具体来说,我们初始化 $h(u)$ 为 $u$ 到 $t$ 的最短距离;特别地, $h(s)=n$ 。 在 BFS 的同时我们顺便检查图的连通性,排除无解的情况。 Loading @@ -464,8 +461,7 @@ HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可 HLPP 推送的条件是 $h(u)=h(v)+1$ ,而如果在算法的某一时刻, $h(u)=t$ 的结点个数为 $0$ ,那么对于 $h(u)>t$ 的结点就永远无法推送超额流到 $t$ ,因此只能送回 $s$ ,那么我们就在这时直接让他们的高度变成 $n+1$ ,以尽快推送回 $s$ ,减少重贴标签的操作。 #### LuoguP4722【模板】最大流 加强版/预流推进 ??? "LuoguP4722【模板】最大流 加强版/预流推进" ```cpp #include <cstdio> #include <cstring> Loading docs/graph/flow/min-cost.md +45 −53 Original line number Diff line number Diff line Loading @@ -24,8 +24,7 @@ 相当于把 $w(u,v)$ 作为边权,在残存网络上求最短路 #### 核心代码 ???+ "核心代码" ```cpp struct qxx { int nex, t, v, c; Loading @@ -39,7 +38,6 @@ void add_flow(int f, int t, int v, int c) { add_path(f, t, v, c); add_path(t, f, 0, -c); } int dis[N], pre[N], incf[N]; bool vis[N]; bool spfa() { Loading Loading @@ -72,19 +70,15 @@ void update() { ## 类 Dinic 算法 我们可以在 $\text{Dinic}$ 算法的基础上进行改进,把 $\text{BFS}$ 求分层图改为用 $\text{SPFA}$ (由于有负权边,所以不能直接用 $\text{Dijkstra}$ )来求一条单位费用之和最小的路径,也就是把 $w(u,v)$ 当做边权然后在残量网络上求最短路,当然在 $\text{DFS}$ 中也要略作修改。这样就可以求得网络流图的 **最小费用最大流** 了。 我们可以在 Dinic 算法的基础上进行改进,把 BFS 求分层图改为用 SPFA(由于有负权边,所以不能直接用 Dijkstra)来求一条单位费用之和最小的路径,也就是把 $w(u,v)$ 当做边权然后在残量网络上求最短路,当然在 DFS 中也要略作修改。这样就可以求得网络流图的 **最小费用最大流** 了。 如何建 **反向边** ?对于一条边 $(u,v,w,c)$ (其中 $w$ 和 $c$ 分别为容量和费用),我们建立正向边 $(u,v,w,c)$ 和反向边 $(v,u,0,-c)$ (其中 $-c$ 是使得从反向边经过时退回原来的费用)。 **优化** :如果你是“关于 $\text{SPFA}$ ,它死了”言论的追随者,那么你可以使用 $\text{Primal-Dual}$ 原始对偶算法将 $\text{SPFA}$ 改成 $\text{Dijkstra}$ ! **优化** :如果你是“关于 SPFA,它死了”言论的追随者,那么你可以使用 Primal-Dual 原始对偶算法将 SPFA 改成 Dijkstra! **时间复杂度** :可以证明上界为 $O(nmf)$ ,其中 $f$ 表示流量。 * * * ### 代码 ??? "最小费用最大流" ???+ "代码实现" ```cpp #include <algorithm> #include <cstdio> Loading Loading @@ -154,8 +148,6 @@ void update() { } ``` * * * ## 习题 - [「Luogu 3381」【模板】最小费用最大流](https://www.luogu.org/problemnew/show/P3381) Loading Loading
docs/ds/dsu.md +2 −4 Original line number Diff line number Diff line Loading @@ -20,9 +20,7 @@ void makeSet(int size) { ## 查找 !!! 举个例子 几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 在这样的思想下,并查集的查找算法诞生了。 Loading Loading @@ -55,7 +53,7 @@ int find(int x) { } ``` 不太懂的话我们就上两张图吧 上两张图:  Loading
docs/graph/bridge.md +0 −2 Original line number Diff line number Diff line Loading @@ -46,8 +46,6 @@ low[u] = min(low[u], num[v]); [洛谷 P3388【模板】割点(割顶)](https://www.luogu.org/problemnew/show/P3388) ### Code ??? "例题代码" ```cpp /* Loading
docs/graph/flow/max-flow.md +135 −139 Original line number Diff line number Diff line Loading @@ -332,11 +332,13 @@ struct ISAP { ## Push-Relabel 预流推进算法 该方法在求解过程中忽略流守恒性,并每次对一个结点更新信息,以求解最大流 该方法在求解过程中忽略流守恒性,并每次对一个结点更新信息,以求解最大流。 有 HLPP 的主流算法 ### 通用的预流推进算法 推送 - 重贴标签算法通过对单个结点的更新操作,直到没有结点需要更新来求解最大流 首先我们介绍预流推进算法的主要思想,以及一个可行的暴力实现算法。 预流推进算法通过对单个结点的更新操作,直到没有结点需要更新来求解最大流。 算法过程维护的流函数不一定保持流守恒性,对于一个结点,我们允许进入结点的流超过流出结点的流,超过的部分被称为结点 $u(u\in V-\{s,t\})$ 的 **超额流** $e(u)$ : Loading @@ -344,24 +346,24 @@ $$ e(u)=\sum_{(x,u)\in E}f(x,u)-\sum_{(u,y)\in E}f(u,y) $$ 若 $e(u)>0$ ,称结点 $u$ **溢出** . 若 $e(u)>0$ ,称结点 $u$ **溢出** 。 推送 - 重贴标签算法维护每个结点的高度 $h(u)$ ,并且规定溢出的结点 $u$ 如果要推送超额流,只能向高度小于 $u$ 的结点推送;如果 $u$ 没有相邻的高度小于 $u$ 的结点,就修改 $u$ 的高度(重贴标签)。 预流推进算法维护每个结点的高度 $h(u)$ ,并且规定溢出的结点 $u$ 如果要推送超额流,只能向高度小于 $u$ 的结点推送;如果 $u$ 没有相邻的高度小于 $u$ 的结点,就修改 $u$ 的高度(重贴标签)。 #### 高度函数 准确地说,推送 - 重贴标签维护以下的一个映射 $h:V\to \mathbf{N}$ : 准确地说,预流推进维护以下的一个映射 $h:V\to \mathbf{N}$ : - $h(s)=|V|,h(t)=0$ - $\forall (u,v)\in E_f,h(u)\leq h(v)+1$ 则称 $h$ 是残存网络 $G_f=(V_f,E_f)$ 的高度函数。 称 $h$ 是残存网络 $G_f=(V_f,E_f)$ 的高度函数。 引理 1:设 $G_f$ 上的高度函数为 $h$ ,对于任意两个结点 $u,v\in V$ ,如果 $h(u)>h(v)+1$ ,则 $(u,v)$ 不是 $G_f$ 中的边。 算法只会在 $h(u)=h(v)+1$ 的边执行推送。 #### 推送 -Push #### 推送(Push) 适用条件:结点 $u$ 溢出,且存在结点 $v((u,v)\in E_f,c(u,v)-f(u,v)>0,h(u)=h(v)+1)$ ,则 push 操作适用于 $(u,v)$ 。 Loading @@ -369,7 +371,7 @@ $$ 如果 $(u,v)$ 在推送完之后满流,将其从残存网络中删除。 #### 重贴标签 -Relabel #### 重贴标签(Relabel) 适用条件:如果结点 $u$ 溢出,且 $\forall (u,v)\in E_f,h(u)\leq h(v)$ ,则 relabel 操作适用于 $u$ 。 Loading @@ -394,11 +396,9 @@ $$ 上述将 $(s,v)\in E$ 充满流,并将 $h(s)$ 抬高,使得 $(s,v)\notin E_f$ ,因为 $h(s)>h(v)$ ,而且 $(s,v)$ 毕竟满流,没必要留在残存网络中;上述还将 $e(s)$ 初始化为 $\sum_{(s,v)\in E}f(s,v)$ 的相反数。 #### 通用执行框架 无需按照特定顺序,执行以下过程: #### 通用算法 - 只要存在结点 $u$ 满足 push 或 relabel 的条件,就执行对应的操作。 我们每次扫描整个图,只要存在结点 $u$ 满足 push 或 relabel 操作的条件,就执行对应的操作。 如图,每个结点中间表示编号,左下表示高度值 $h(u)$ ,右下表示超额流 $e(u)$ ,结点颜色的深度也表示结点的高度;边权表示 $c(u,v)-f(u,v)$ ,绿色的边表示满足 $h(u)=h(v)+1$ 的边 $(u,v)$ (即残存网络的边 $E_f$ ): Loading @@ -414,8 +414,7 @@ $$ 可以发现,最后的超额流一部分回到了 $s$ ,且除了源点汇点,其他结点都没有溢出;这时的流函数 $f$ 满足流守恒性,为最大流,即 $e(t)$ 。 #### 核心代码 ???+ "核心代码" ```cpp const int N = 1e4 + 4, M = 1e5 + 5, INF = 0x3f3f3f3f; int n, m, s, t, maxflow, tot; Loading Loading @@ -443,20 +442,18 @@ void relabel(int u) { ### HLPP 算法 最高标号预流推进算法(High Level Preflow Push)是基于推送 - 重贴标签算法的优先队列实现,该算法优先推送高度高的溢出的结点,算法算法复杂度 $O(n^2\sqrt m)$ 。 最高标号预流推进算法(High Level Preflow Push)是基于预流推进算法的优先队列实现,该算法优先推送高度高的溢出的结点,算法算法复杂度 $O(n^2\sqrt m)$ 。 具体地说,HLPP 维护以下过程: 具体地说,HLPP 算法过程如下: 1. 初始化(基于推送 - 重贴标签算法); 1. 初始化(基于预流推进算法); 2. 选择溢出结点(除 $s,t$ )中高度最高的结点 $u$ ,并对它所有可以推送的边进行推送; 3. 如果 $u$ 仍溢出,对它重贴标签,回到 2; 3. 如果 $u$ 仍溢出,对它重贴标签,回到步骤 2; 4. 如果没有溢出的结点,算法结束。 #### BFS 优化 HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可以在初始化高度的时候进行优化: 具体来说,我们初始化 $h(u)$ 为 $u$ 到 $t$ 的最短距离;特别地, $h(s)=n$ 。 HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可以在初始化高度的时候进行优化。具体来说,我们初始化 $h(u)$ 为 $u$ 到 $t$ 的最短距离;特别地, $h(s)=n$ 。 在 BFS 的同时我们顺便检查图的连通性,排除无解的情况。 Loading @@ -464,8 +461,7 @@ HLPP 的上界为 $O(n^2\sqrt m)$ ,但在使用时卡得比较紧;我们可 HLPP 推送的条件是 $h(u)=h(v)+1$ ,而如果在算法的某一时刻, $h(u)=t$ 的结点个数为 $0$ ,那么对于 $h(u)>t$ 的结点就永远无法推送超额流到 $t$ ,因此只能送回 $s$ ,那么我们就在这时直接让他们的高度变成 $n+1$ ,以尽快推送回 $s$ ,减少重贴标签的操作。 #### LuoguP4722【模板】最大流 加强版/预流推进 ??? "LuoguP4722【模板】最大流 加强版/预流推进" ```cpp #include <cstdio> #include <cstring> Loading
docs/graph/flow/min-cost.md +45 −53 Original line number Diff line number Diff line Loading @@ -24,8 +24,7 @@ 相当于把 $w(u,v)$ 作为边权,在残存网络上求最短路 #### 核心代码 ???+ "核心代码" ```cpp struct qxx { int nex, t, v, c; Loading @@ -39,7 +38,6 @@ void add_flow(int f, int t, int v, int c) { add_path(f, t, v, c); add_path(t, f, 0, -c); } int dis[N], pre[N], incf[N]; bool vis[N]; bool spfa() { Loading Loading @@ -72,19 +70,15 @@ void update() { ## 类 Dinic 算法 我们可以在 $\text{Dinic}$ 算法的基础上进行改进,把 $\text{BFS}$ 求分层图改为用 $\text{SPFA}$ (由于有负权边,所以不能直接用 $\text{Dijkstra}$ )来求一条单位费用之和最小的路径,也就是把 $w(u,v)$ 当做边权然后在残量网络上求最短路,当然在 $\text{DFS}$ 中也要略作修改。这样就可以求得网络流图的 **最小费用最大流** 了。 我们可以在 Dinic 算法的基础上进行改进,把 BFS 求分层图改为用 SPFA(由于有负权边,所以不能直接用 Dijkstra)来求一条单位费用之和最小的路径,也就是把 $w(u,v)$ 当做边权然后在残量网络上求最短路,当然在 DFS 中也要略作修改。这样就可以求得网络流图的 **最小费用最大流** 了。 如何建 **反向边** ?对于一条边 $(u,v,w,c)$ (其中 $w$ 和 $c$ 分别为容量和费用),我们建立正向边 $(u,v,w,c)$ 和反向边 $(v,u,0,-c)$ (其中 $-c$ 是使得从反向边经过时退回原来的费用)。 **优化** :如果你是“关于 $\text{SPFA}$ ,它死了”言论的追随者,那么你可以使用 $\text{Primal-Dual}$ 原始对偶算法将 $\text{SPFA}$ 改成 $\text{Dijkstra}$ ! **优化** :如果你是“关于 SPFA,它死了”言论的追随者,那么你可以使用 Primal-Dual 原始对偶算法将 SPFA 改成 Dijkstra! **时间复杂度** :可以证明上界为 $O(nmf)$ ,其中 $f$ 表示流量。 * * * ### 代码 ??? "最小费用最大流" ???+ "代码实现" ```cpp #include <algorithm> #include <cstdio> Loading Loading @@ -154,8 +148,6 @@ void update() { } ``` * * * ## 习题 - [「Luogu 3381」【模板】最小费用最大流](https://www.luogu.org/problemnew/show/P3381) Loading