[TOC]c++
by 沉迷流体力学无意刷题的rvalue算法
€€£ WARNING: 前方多图杀猫数组
原本这个是18年12月在校内讲课用的课件...原本打算当时就放到博客上可是由于里面有大量mermaid图就没敢放qaq...然而忽然发现cnblogs是滋磁mermaid图的因而就丢出来了qaq网络
若是有错漏之处还请在评论区指正.函数
好像因为目录结构太大因此cnblogs生成不出来了...大纲是长这样的:工具
+ 网络流从入门到放弃 + 何谓网络流 + 最大流 + Ford-Fulkerson算法(增广路算法) + 实现 + 局限 + Edmonds-Karp算法(EK算法) + 代码实现 + 局限 + Dinic算法 + 举个栗子 + 阻塞流 + 代码实现 + 时间复杂度分析 + 真·时间复杂度分析 + 真·代码实现 + 一些题外问题 + 最大流建模举例 + 圆桌问题 + 公平分配问题 + 星际转移问题 + 收集者的难题 + 最大流与最小割 + 流与割的关系 + 最大流最小割定理 + 最小割建模举例 + 花园的守护之神 + 王者之剑 + Number + 最小割 + 最大权闭合子图 + 切糕 + 最小费用最大流 + EK费用流 + 反向边权 + 关于SPFA + 时间复杂度分析 + 消圈算法 + 消圈定理 + ZKW费用流 + 代码实现 + 关于当前弧优化 + 时间复杂度分析 + Dijkstra费用流 + 最小费用最大流建模举例 + 南极科考旅行 + 餐巾 + 负载平衡 + 最长k可重区间集 + 平方费用最小费用最大流
文章长度比较劝退...可是应该能真正让读者入门(至少能打出复杂度比较正确的板子).测试
上下界网络流部分因为一些历史缘由咕给另外一个聚聚了...优化
假设 \(G = (V,E)\) 是一个有限的有向图,它的每条边 \((u,v) \in E\) 都有一个非负值实数的容量$ c(u, v)\(。若是\) (u, v) \not \in E$,咱们假设$ c(u, v) = 0$。咱们区别两个顶点:一个源点 \(s\) 和一个汇点$ t$。一道网络流是一个对于全部结点$ u$ 和$ v \(都有如下特性的实数函数\) f:V \times V \rightarrow \mathbb$: 容量限制(Capacity Constraints):$ f(u, v) \leq c(u, v)$一条边的流不能超过它的容量。 斜对称(Skew Symmetry):$ f(u, v) = - f(v, u)$由 $u$到 $v$的净流必须是由 $v$到 $u$的净流的相反(参考例子)。 流守恒(Flow Conservation): 除非 $u = s$或 \(u = t\),不然 $\sum_{w \in V} f(u, w) = 0$一结点的净流是零,除了“制造”流的源点和“消耗”流的汇点。 即流守恒意味着:$ \sum_{(u,v) \in E} f(u,v) = \sum_{(v,z) \in E} f(v,z)$ ,对每一个顶点$ v \in V\setminus{s,t}$ 注意$ f(u,v)$ 是由 \(u\) 到 $v \(的净流。若是该图表明一个实质的网络,由\) u \(到\) v \(有4单位的实际流及由\) v$ 到 $u $有3单位的实际流,那么 $f(u, v) = 1 $及 \(f(v, u) = -1\)。 基本上,咱们能够说,物理网络的网络流是从$ s = \sum_{(s,v)\in E} f(s,v) $出发的流 边的剩余容量(residual capacity)是$ c_f(u, v) = c(u, v) - f(u, v)$。这定义了以 $G_f(V, E_f) $表示的剩余网络(residual network),它显示可用的容量的多少。留意就算在原网络中由 \(u\) 到 $v \(没有边,在剩余网络仍可能有由\) u \(到\) v $的边。由于相反方向的流抵消,减小由 $v \(到\) u$ 的流等于增长由$ u$ 到 $v \(的流。**增广路(augmenting path)**是一条路径\) (u_1, u_2, \dots, u_k)$,而 \(u_1 = s\)、$ u_k = t $及 \(c_f(u_i, u_{i+1}) > 0\),这表示沿这条路径发送更多流是可能的。当且仅当剩余网络$ G_f $没有增广路时处于最大流。 所以以下使用图 \(G\) 建立 \(G_f\): $G_f = V $的顶点 定义以下的 $G_f = E_f $的边 对每条边$ (x,y) \in E$ 若$ f(x,y) < c(x,y)\(,建立容量为\) c_f = c(x,y) - f(x,y)\(的前向边\)(x,y) \in E_f$。 若$ f(x,y) > 0$,建立容量为$ c_f = f(x,y) $的后向边 \((y, x) \in E_f\)。 这个概念用在计算流量网的最大流的Ford–Fulkerson算法中。 有时须要对有多于一个源点的网络,因而就引入了图的超源点。这包含了一个与无限容量的边链接的每一个源点,以做为一个总体源点。汇点也有相似的构造称做超汇点。ui
以上是摘自Wikipedia的定义google
实际上重点有三:
想象一个不可压缩的流体的运输管网, 每一个管道都有防倒流阀门 (保证有向) , 每一个管道还有一个单位时间内的流量限制, 那就是一个网络流模型的样子了
最大流问题其实就是字面意思, 求 \(s\) 到 \(t\) 的最大流量.
好比对于下面这个流量网络:
它的最大流是 $23$:
对于最大流问题, 咱们有一个直观的思路, 就是找一条从 \(s\) 到 \(t\) 并且剩余容量非空的路径, 而后把它跑满并累加答案
而这个"从 \(s\) 到 \(t\) 并且剩余容量非空的路径"就是增广路. 由于它将原有的流扩充(或者说"增广")了.
可是这个时候咱们会发现一些问题: 若是不巧找到了一个增广路把原本不应在最大流里的边给增广了怎么破?
好比下图中的增广路:
若是咱们继续增广, 则咱们获得的"最大流"只有 $20$.
这时咱们考虑之前讲过的"可撤销贪心", 创建一条反向边来容许撤销.
记得定义中的一句"斜对称"么? 也就是 \(f(u,v)=-f(v,u)\), 因而咱们能够定义反向边上增长一单位流量都表明原边上减小一单位流量. 若是增广出了 \((u,v)\) 之间的双向流量实际上能够将通过 \(u,v\) 的流量交换来抵消成单向流量. 好比下图:
实际上咱们至关因而增长反方向的流量来把原来的正向流量"推回去".
或者一个更形象化的解释, 每条边表明着一个里面流动着不可压缩流体的具备流速限制的管道, 那么反向增广就有点像是反向加压来尝试把流体压回原来的点, 总的效果就是让这条边里的流速减缓或反向, 而让流返回原来的点从新决策.
这其实也告诉咱们, 最大流一定是无环的. 由于循环流无源无汇, 不会对 \(s\verb|-|t\) 流量产生贡献, 却会白白占用容量, 必定不会优.
实际上咱们对这个"消除环流"的过程的实现方式是"只要给一条边加载上流量, 就必须在其对应的反向边上扩充等量的容量".
这个过程当中引入一个概念: 残量网络. 能够理解为意思就是把满流的边扔掉以后, 边权为剩余流量的图.
因而咱们加载流量的过程就能够转化为边权下降, 扩容的过程就能够转化为边权增长.
实际上整个最大流的过程当中咱们咱们并无必要一直在容量和当前流量这两个值上作文章. 咱们在整个算法中全程只关心它们的差值: 剩余容量. 因此咱们其实只记录它就能够了. 而后残量网络就能够定义成只包含剩余容量非 $0$ 的边的图.
创建流量网络, 对于每条边记录一下它的出入结点/剩余容量/反向边, 而后咱们进行一次只走剩余流量非 $0$ 的边的DFS来查找增广路径, 过程当中顺便维护一下路径上的最小剩余容量 (显然咱们只要找到一条增广路径以后把它压榨干净再继续找下一条是最优策略), 回溯的时候进行 "减小当前边剩余容量, 增长反向边剩余容量" 的操做, 结束后把答案累加起来就行了. 代码实现就是返回一个值表示找到的增广路的流量, 若是为 $0$ 表示没有找到增广路. 只要返回非 $0$ 值就执行缩小剩余容量的操做并回溯. 单次增广是 \(O(V+E)\) 的.
不难发现整个过程当中一直保持着流量守恒的原则: 每次咱们都是在一整条路径上搬移流量, 因此中间结点彻底不会累积流量.
咱们发现增广路算法的运行时间基本上全靠RP: 看你增广路选得怎么样. 选得好没准一次就跑出来了, 选差了就得一直把流推来推去. 好比下面这张图:
咱们一眼就能看出这图最大流是 $46666666$ , 可是假如你RP爆炸一直在和边 \((1,2)\) 斗智斗勇的话...请容许我作一个悲伤的表情...
不过增广路算法是必定会结束的, 由于你每次找到一个增广路都会让流量增长, 最终必定能达到最大流.
时间复杂度是 O(值域) 的, 属于指数级算法. (题外话: 其实O(值域)的算法全是指数算法...我才不会告诉你今年联赛day1考了个NPC问题呢)
可是这个算法是几乎全部实用网络流算法的基础. 它们本质上都是增广路算法的优化.
EK算法其实就是在增广路算法的基础上加了一层优化: 每次增广最短的增广路.
这样的话它的时间复杂度便有了保障, 是 \(O(VE^2)\) 的. 大致的证实思路是这样的:
首先每次咱们找到一条增广路的时候确定会把它压榨干净, 也就是说至少有一条边在这个过程当中被跑满了. 咱们把这样的边叫作关键边. 增广以后, 这些边在残量网络中都会被无视掉. 也就是说这条路径就这么断掉了. 而咱们每次都增广最短路, 也就是说咱们每次都在破坏最短路. 因此 \((s,t)\) 之间的最短路单调递增.
由于增广路必然伴随至少一条关键边出现, 因此咱们能够把增广过程的迭代次数上界转化为每条边成为关键边的次数.
由于关键边会从残量网络中消失, 直到反向边上有了流量才会恢复成为关键边的可能性, 而当反向边上有流量时最短路长度必定会增长. 而最短路长度不会超过 \(V\), 因此总迭代次数是 \(O(VE)\) 的.
由于EK算法中残量网络上的最短路是 $0/1$ 最短路, 直接BFS实现的话时间复杂度是 \(O(E)\) 的, 因而EK算法总时间复杂度 \(O(VE^2)\) , 证毕.
实现上把DFS改为BFS而且在第一次访问到 \(t\) 时就跳出就好了, 实际上没啥好讲的...
下面这个实现是去年粘的某刚哥届学长的课件里的...懒得本身写了(实际上是不会)
int Bfs() { memset(pre, -1, sizeof(pre)); for(int i = 1 ; i <= n ; ++ i) flow[i] = INF; queue <int> q; pre[S] = 0, q.push(S); while(!q.empty()) { int op = q.front(); q.pop(); for(int i = 1 ; i <= n ; ++ i) { if(i==S||pre[i]!=-1||c[op][i]==0) continue; pre[i] = op; //找到未遍历过的点 flow[i] = min(flow[op], c[op][i]); // 更新路径上的最小值 q.push(i); } } if(flow[T]==INF) return -1; return flow[T]; } int Solve() { int ans = 0; while(true) { int k = Bfs(); if(k==-1) break; ans += k; int nw = T; while(nw!=S) {//更新残余网络 c[pre[nw]][nw] -= k, c[nw][pre[nw]] += k; nw = pre[nw]; } } return ans; }
虽然EK算法成功把时间复杂度降到了一个多项式级别, 可是它 \(O(VE^2)\) 的上界实际上至关于 \(O(V^5)\) 的级别(\(E\) 与 \(V^2\) 能够是同阶的), 并非很是可以使人接受.
咱们来看一看为啥EK依然这么慢.
为啥呢?
咱们再看最开始的流量网络, 跑一跑EK的BFS求最短路, 找到一条最短增广路 \(s\rightarrow 1 \rightarrow 3 \rightarrow t\) 以后:
咱们增广掉这条路径上的 $12$ 单位流量...而后再来一遍BFS求最短路...
先等一等!
咱们一眼就能就发现: 这 \(s \rightarrow 2 \rightarrow 4 \rightarrow t\) 明明也是一条最短增广路啊!
EK算法的劣势就在于, 每次遍历了一遍残量网络以后只能求出一条增广路来增广.
因而咱们针对这一点进行优化, 咱们就获得了一个更加优秀的替代品.
没有什么是一个BFS或一个DFS解决不了的;若是有,那就两个一块儿。
Dinic算法又称为"Dinic阻塞流算法", 这个算法的关键就在于"阻塞流".
首先咱们顺着EK算法的思路, 每次增广最短路上的边来在必定程度上保证时间复杂度.
这时咱们引入一个概念: 分层图. 它的一个不严谨的定义就是: 对于每一个点, 按照从 \(s\) 到它的最短路长度分组, 每组即为"一层".
其实这个"分层"也能够理解为深度...
回到最开始的那个流量网络, 咱们把它BFS一遍按照 \(s\) 最短路分层, 获得下面的图:
层内的边和反向边不在最短路上因此增广时都会被咱们被无视, 咱们在示意图中删掉它们, 因而就变成:
分好层以后, 咱们很是偷税地发现:
由于不用担忧在处理分层图的时候流会被反向边退回, 因此咱们只管放心大胆地只用一遍DFS来增广就行了.
可是这并不意味着咱们在边上加载流量的时候能够无论反向边, 反向边的残量变动仍是要算的, 由于之后的DFS可能会把流再推回去.
这一节刚开始的时候说Dinic的关键就在于"阻塞流". 阻塞流是啥?
咱们尝试在上图中增广, 而后获得下面的残量网络:
咱们发现把当前分层图上的最大流彻底增广以后, \(s\) 和 \(t\) 在分层图上必定会不连通 (增广路算法找不到新的增广路就是由于残量网络不连通), 咱们称其为阻塞增广. 这样增广出来的流就是阻塞流.
int Dinic(int s,int t){ int ans=0; while(BFS(s,t)) ans+=DFS(s,INF,t); return ans; } int DFS(int s,int flow,int t){ if(s==t||flow<=0) return flow; int rest=flow; for(Edge* i=head[s];i!=NULL&&rest>0;i=i->next){ if(i->flow>0&&depth[i->to]==depth[s]+1){ int k=DFS(i->to,std::min(rest,i->flow),t); rest-=k; i->flow-=k; i->rev->flow+=k; } } return flow-rest; } bool BFS(int s,int t){ memset(depth,0,sizeof(depth)); std::queue<int> q; q.push(s); depth[s]=1; while(!q.empty()){ s=q.front(); q.pop(); for(Edge* i=head[s];i!=NULL;i=i->next){ if(i->flow>0&&depth[i->to]==0){ depth[i->to]=depth[s]+1; if(i->to==t) return true; q.push(i->to); } } } return false; }
BFS
函数很是好说, 就是一个纯粹的 $0/1$ 最短路
DFS
函数有几个点须要说一下. 先说参数. 首先 s
, t
是字面意思, 而后是flow
参数, 表明"上游的边对增广路上的流量的限制". 由于增广路上任意一条边都不能超流. 接着有一个局部变量 rest
, 表示"上游的流量限制还有rest
单位没有下传". 由于要知足流量守恒, 咱们只能把流入的流量分配到后面, 因此这个 rest
实际上保存的就是最大可能的流入流量.
最后返回的是flow-rest
, 把全部后继结点都访问过以后的 rest
值即为没法排出的流入量的值, 咱们返回的是增广量因此确定不能让它不能排出, 因此咱们把上游流入量减去不能排出的量即为可行流量.
或者说, 参数 flow
是"推送量", 返回的是"接受量"或"成功传递给 \(t\) 的流量", rest
是"淤积量".
(能量流动学傻了.png)
因为阻塞增广以后再也不存在原长度的最短路, 最短路的长度至少 \(+1\). 因此阻塞增广会进行进行 \(O(V)\) 次.
进行一次阻塞增广只要一次DFS就能够实现, 而一次DFS的时间复杂度小学生都知道是 \(O(E)\) 的.
因而Dinic的时间复杂度是 \(O(VE)\) 的! 比EK优秀到不知道哪里去了!
然而是假的
其实多路增广的同时咱们发现一个问题: 这个DFS求阻塞增广的复杂度其实和普通DFS彻底不一样. 想想, 为何.
EK算法中计算一条增广路是严格BFS一遍, 时间复杂度严格 \(O(E)\), 可是此次的DFS就不是这样了.
咱们会有重复DFS一个结点这种操做.
好比下面这个分层图:
咱们就会发现一开始从 \(s \rightarrow 1 \rightarrow 3\) 这条路径过来的时候, 上游的残量已经把最大增广量卡到了 $4$. 因此咱们只能在 $3$ 号点后增广 $4$ 个单位的流量, 可是 $3$ 号点依然有继续增广的空间, 咱们不能打个vis
就跑路. 接着从 \(s \rightarrow 2 \rightarrow 3\) 过来的时候, 就会继续从 $3$ 号点向下增广.
然而不加vis
的DFS是指数级的.
因此这个鬼Dinic又是一个辣鸡指数算法?
没那么简单.
咱们在DFS的时候加两个很是简单可是很重要的优化:
int DFS(int s,int flow,int t){ if(s==t||flow<=0) return flow; int rest=flow; for(Edge*& i=cur[s];i!=NULL;i=i->next){ if(i->flow>0&&depth[i->to]==depth[s]+1){ int tmp=DFS(i->to,std::min(rest,i->flow),t); if(tmp<=0) depth[i->to]=0; rest-=tmp; i->flow-=tmp; i->rev->flow+=tmp; if(rest<=0) break; } } return flow-rest; }
若是咱们令 depth=0
, 那么不可能会有哪一个前驱结点知足 \(d(u)+1=d(v)\) 了, 也就是说咱们把这个点无视掉了.
为啥呢?
由于你如今找不到妹子之后也同样找不到
(这是刚哥的比喻(逃))
由于若是你如今尝试给 i->to
推送一个大小非 $0$ 的流量, 然而它却无情地返回了一个 $0$ 做为接受量的话, 只能说明: i->to
结点今后刻开始没法再推送更多流量到 \(t\) 了. 如今不能, 之后也不能. 因而咱们删掉它做为优化.
而后最关键的决定时间复杂度的优化是当前弧优化, 咱们每次向某条边的方向推流的时候, 确定要么把推送量用完了, 要么是把这个方向的容量榨干了. 除了最后一条由于推送量用完而没法继续增广的边以外其余的边必定没法继续传递流量给 \(t\) 了. 这种无用边会在寻找出边的循环中增大时间复杂度(记得那个欧拉路题么?), 必须删除.
最后再看从新这一整个DFS的过程, 若是当前路径的最后一个点能够继续扩展, 则确定是在层间向汇点前进了一步, 最多走 \(V\) 步就会到达汇点. 在前进过程当中, 咱们发现一个点没法再向 \(t\) 传递流量, 咱们就删掉它. 根据咱们在分析EK算法时间复杂度的时候获得的结论, 咱们会找到 \(O(E)\) 条不一样的增广路, 每条增广路又会前进或后退 \(O(V)\) 步来更新流量, 又由于咱们加了当前弧优化因此查找一条增广路的时间是和前进次数同阶的, 因而单次阻塞增广DFS的过程的时间上界是 \(O(VE)\) 的.
因而Dinic算法的总时间复杂度是 \(O(V^2E)\) 的.
这个上界很是很是松 (王逸松的松). 松到什么程度?
LOJ最大流板子, \(V=100, E=5000\) , 计算得 \(V^2E=5\times 10^7\) , 而我这份充满STL和递归的板子代码实际上跑得最慢的点只跑了 $25\texttt$.
顺便说这个Dinic在容量 $0/1$ 以及层数很少的图上跑得更快, 二分图匹配问题上甚至被证实了一个 \(O(\sqrt{V}E)\) 的上界
实战应用网络流建模的时候由于是本身构图, 通常层数都不会很是大并且结构是本身定的, 因此跑得会更快~ (通常$800$ 到 $1000$ 个点, 边数 $1\times 10^4$ 左右的图都是能跑的)
如下是LOJ#101 最大流的AC板子
#include <bits/stdc++.h> const int MAXV=110; const int MAXE=10010; const long long INF=1e15; struct Edge{ int from; int to; int flow; Edge* rev; Edge* next; }; Edge E[MAXE]; Edge* head[MAXV]; Edge* cur[MAXV]; Edge* top=E; int v; int e; int s; int t; int depth[MAXV]; bool BFS(int,int); void Insert(int,int,int); long long Dinic(int,int); long long DFS(int,long long,int); int main(){ scanf("%d%d%d%d",&v,&e,&s,&t); for(int i=0;i<e;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); Insert(a,b,c); } printf("%lld\n",Dinic(s,t)); return 0; } long long Dinic(int s,int t){ long long ans=0; while(BFS(s,t)) ans+=DFS(s,INF,t); return ans; } bool BFS(int s,int t){ memset(depth,0,sizeof(depth)); std::queue<int> q; q.push(s); depth[s]=1; cur[s]=head[s]; while(!q.empty()){ s=q.front(); q.pop(); for(Edge* i=head[s];i!=NULL;i=i->next){ if(i->flow>0&&depth[i->to]==0){ depth[i->to]=depth[s]+1; cur[i->to]=head[i->to]; if(i->to==t) return true; q.push(i->to); } } } return false; } long long DFS(int s,long long flow,int t){ if(s==t||flow<=0) return flow; long long rest=flow; for(Edge*& i=cur[s];i!=NULL;i=i->next){ if(i->flow>0&&depth[i->to]==depth[s]+1){ long long tmp=DFS(i->to,std::min(rest,(long long)i->flow),t); if(tmp<=0) depth[i->to]=0; rest-=tmp; i->flow-=tmp; i->rev->flow+=tmp; if(rest<=0) break; } } return flow-rest; } void Insert(int from,int to,int flow){ top->from=from; top->to=to; top->flow=flow; top->rev=top+1; top->next=head[from]; head[from]=top++; top->from=to; top->to=from; top->flow=0; top->rev=top-1; top->next=head[to]; head[to]=top++; }
注意到我把当前弧优化的重赋值部分写在了 BFS
里, 这样能够作到"按需赋值". 由于Dinic原本上界就松得一匹, BFS的过程当中不连通的点根本就不用再管了...
以及若是你用数组边表的话, 能够选择让下标从 $0$ 开始, 保证一次加入两个边, 这样的话只要 \(\text{xor}\,1\) 就能够算出反向边的编号了
到如今估计jjm已经吵了好几回"这是个黑盒算法不用理解"之类的话了.
为啥我要把一个Dinic讲得这么细呢? 何况它确实真的只是个建模以后跑一下的工具?
由于若是你不理解Dinic的过程与复杂度分析, 你几乎一 定 会 写 假.
有些看起来很不起眼的小细节可能影响着整个算法的时间复杂度.
首先就是当前弧优化的跳出条件, 我为啥要把"除了最后一条边以外"那句话加粗呢? 由于你若是把跳出断定写在for
循环里会慢 $10$ 倍以上, 根本不是常数问题, 是复杂度出了锅. 由于你会漏掉最后那个可能没跑满的弧, 而分层图BFS会在当前图没有被割断的时候一直跑跑跑, 因而就锅了.
其次有人把无用点优化当成当前弧优化的替代品, 实际上无用点优化并不能保证时间复杂度. 无用点优化能够看作是当前弧优化的一个弱化版, 区别只是在因而出边全都没法传递流量的时候再删仍是一条边没法传递流量时就删.
甚至有人直接不加当前弧优化和无用点优化, 这是我最 \(F_2\) 的...
网上把Dinic写假的真很多. 不少Dinic教程上的板子都是假的.
本着防止你们好不容易建出图来结果由于板子一直写的是假的结果炸飞的态度 (坚信一个复杂度是假的的东西复杂度是真的, 这和在普通最短路中用SPFA有什么区别) , 我决定从仔细讲好Dinic开始.
以及为啥不讲ISAP?
由于我不是ISAP选手因此不会ISAP
由于ISAP其实在不少用途上其实并无Dinic灵活. 有些玄学建图骚操做ISAP很难跑得起来. 今年省选D2T1若是不是由于出题人是ISAP选手而特地构造了一个ISAP能跑的题的话极可能就会无心间卡掉ISAP选手 (这不是玩笑, 多半正经出题人都是Dinic选手, 一个不当心就会无心间卡掉ISAP). 因此我我的并不建议你们作ISAP选手.
固然你要是坚信ISAP跑得比较快并且不会被卡而去学ISAP我也不拦你...柱子恒就是ISAP选手
当你能熟练而正确地打出最大流板子的时候你就会发现: 你并不能作出几道网络流题目.
网络流的精髓在于建图.
咱们先从最基础的"网络流24题"开始.
你们先来看几个例题理解一下建图的基本套路
假设有来自 \(n\) 个不一样单位的表明参加一次国际会议。每一个单位的表明数分别为 \(r_i\) 。会议餐厅共有 \(m\) 张餐桌,每张餐桌可容纳 \(c_i\) 个表明就餐。 为了使表明们充分交流,但愿从同一个单位来的表明不在同一个餐桌就餐。 试给出知足要求的表明就餐方案。
这题建图感受仍是很显然的.
首先能够看出一个明显的二分图模型, 单位是一种点, 餐桌是另外一种点, 表明数量能够看作是流量.
从 \(s\) 连到全部单位点, 容量为表明数量. 单位点和餐桌点之间两两连一条容量为 $1$ 的边表明"同一个单位来的表明不能在同一个餐桌就餐"的限制. 餐桌点再向 \(t\) 连一条容量为餐桌大小的边来限制这个餐桌的人数.
这样的话, 每一单位流量都表明着一个表明. 它们流经的点和边就表明了它们的特征. 而容量就是对表明的限制.
回想DP, DP的特色就是抽象出一个基本对象"状态", 而后用若干维度来描述这个状态的特征, 根据这些特征应用题目中的限制. 而网络流的构图, 则是用一单位流量流动的过程来刻画特征.
这种"把一个基本对象的特征用一单位流量从 \(s\) 流到 \(t\) 的过程来刻画"的思路, 就是网络流的通常建图策略.
至于输出方案, 咱们只要看从单位点到餐桌点的边有哪些满载了, 一条满载的边 \((u,v)\) 意义就是在最优方案中一个来自 \(u\) 表明的单位的表明坐到了 \(v\) 表明的餐桌上.
固然若是最大流没有跑满(最大流的值不等于表明数量之和)的话确定有表明没被分配出去, 断定无解.
把 \(m\) 个任务分配给 \(n\) 个处理器. 其中每一个任务有两个候选处理器, 能够任选一个分配. 要求全部处理器中被分配任务最多的处理器分得的任务最少. 不一样任务的候选处理器保证不一样.
首先咱们要让最大值最小, 容易想到二分答案.
其次咱们能够看到一个明显的二分图模型: \(m\) 个任务和 \(n\) 个处理器. 咱们从 \(s\) 连容量为 $1$ 的边到全部任务, 从任务连边到候选处理器, 从候选处理器连一条容量为二分答案的边到 \(t\) . 只要最大流达到了 \(m\) , 就说明咱们成功在任务数量最多处理器的任务数量不大于二分答案的状况下分配出了 \(m\) 个任务, 执行 \(O(\log m)\) 次便可.
现有 \(n\) 个太空站位于地球与月球之间,且有 \(m\) 艘公共交通太空船在其间来回穿梭。每一个太空站可容纳无限多的人,而每艘太空船 \(i\) 只可容纳 \(H_i\) 我的。每艘太空船将周期性地停靠一系列的太空站,例如:$1,3,4$ 表示该太空船将周期性地停靠太空站 $134134134\cdots$ 每一艘太空船从一个太空站驶往任一太空站耗时均为 $1$。人们只能在太空船停靠太空站(或月球、地球)时上、下船。 初始时全部人全在地球上,太空船全在初始站。试设计一个算法,找出让 \(k\) 人尽快地所有转移到月球上的运输方案。
\(n \leq 20, k\leq 50, m\leq 13\)
在这个题目中, 咱们使用图论算法的另外一个套路: 把状态抽象为结点.
咱们把 "每一天的空间站/星球" 抽象为状态, 从 \(s\) 连一条容量为 \(k\) 的边到第 $0$ 天的地球, 从全部月球点链接一条容量为 \(\infty\) 的边到 \(t\) , 而后对于第 \(i\) 个飞船, 若是第 \(d\) 天停留在空间站 \(u\) 且下一轮要去 \(v\) 空间站, 则从第 \(d\) 天的 \(u\) 向第 \(d+1\) 天的 \(v\) 连一条容量为 \(H_i\) 的边, 计算最大流是否为 \(k\) 便可断定是否可以彻底运输.
网络流断定, 那么二分答案根据答案建若干层点来断定?
太慢辣!
网络流题不少(不包括上一题)都有个特色: 当你发现某个题须要二分的时候, 它多数状况下并不用二分.
由于你的残量网络仍是能够怼几条边进去接着跑的.
因此你只要从小到大枚举答案, 每次建当天的一层点, 増广以后判断一下是否满流就好了.
增量増广通常快的一匹.
Bob和他的朋友从糖果包装里收集贴纸. 这些朋友每人手里都有一些 (可能有重复的) 贴纸, 而且只跟别人交换他所没有的贴纸. 贴纸老是一对一交换.
Bob比这些朋友更聪明, 由于他意识到指跟别人交换本身没有的贴纸并不老是最优的. 在某些状况下, 换来一张重复的贴纸会更划算.
假设Bob的朋友只和Bob交换 (他们之间不交换), 而且这些朋友只会出让手里的重复贴纸来交换它们没有的不一样贴纸. 你的任务是帮助Bob算出它最终能够获得的不一样贴纸的最大数量.
首先咱们发现, Bob所持有的贴纸数量是必定的, 能够转化为流量, 因此咱们只要建边体现一下交换就好了.
由于某单位流量究竟是什么类型的贴纸与交换过程有关, 因此咱们对于每一种贴纸都创建一个结点, 表明"流量流到这里以后即为对应类型的贴纸". 而后咱们只要把可能的类型转移带上容量建上去就好了. 从 \(s\) 连边到贴纸类型点表明Bob一开始拥有的贴纸, 从贴纸类型点连边到 \(t\) 表明到此为止再也不继续交换. 注意从 \(s\) 连出的边容量为拥有的贴纸数量, 但由于咱们要最大化种类数量, 因此连到 \(t\) 的边必须容量为 $1$. 最大流值即为最大种类数.
有时候咱们会发现用最大流的思路并不能建出模型...考虑这样的一个题目:
看着正在被上古神兽们摧残的花园,花园的守护之神――小Bug同窗泪流满面。然而,OIER不相信眼泪,小bug与神兽们的战争将进行到底! 经过google,小Bug得知,神兽们来自遥远的戈壁。为了扭转战局,小Bug决定拖延神兽增援的速度。从戈壁到达花园的路径错综复杂,由若干段双向的小路组成。神兽们经过每段小路都须要一段时间。小Bug能够经过向其中的一些小路投掷小xie来拖延神兽。她能够向任意小路投掷小Xie,并且能够在同一段小路上投掷多只小xie。每只小Xie能够拖延神兽一个单位的时间。即神兽经过整段路程的总时间,等于没有小xie时他们经过一样路径的时间加上路上通过的全部小路上的小xie数目总和。 神兽们是很聪明的。他们会在出发前侦查到每一段小路上的小Xie数目,而后选择总时间最短的路径。小Bug如今很想知道最少须要多少只小Xie,才能使得神兽从戈壁来到花园的时间变长。做为花园中可爱的花朵,你能帮助她吗?
这™是网络流?
咱们发现其实上面那个题的本质就是: 找一些边知足从 \(s\) 到 \(t\) 的任意路径都必须至少通过一条这些边, 同时让这些选中的边的权值最小.
或者换一个说法, 将这些选中的边删去以后, \(s\) 和 \(t\) 再也不连通, 点集 \(V\) 被分割为两部分 \(V_s\) 和 \(V_t\) . 咱们称点集 \((V_s,V_t)\) 为流网络的一个割, 定义它的容量为全部知足 \(u\in V_s, v\in V_t\) 的边 \((u,v)\) 的容量之和.
而对于一个割 \((V_s,V_t)\), 咱们定义一个流的净流量为全部知足 \(u\in V_s, v\in V_t\) 的边 \((u,v)\) 上加载的流量减去全部知足 \(v\in V_s, u\in V_t\) 的边 \((u,v)\) 上加载的流量.
咱们从新拿出最开始的那个流网络, 咱们就能够作出这样的一个割 \((S,T)\):
不难看出,这个割的容量为 $35$, 最大流的净流量为 $12+12-1=23$ .
注意到净流量能够由于反向流量而为负, 可是容量必定是非负的 (反向边和 \(s\rightarrow t\) 的连通性无关)
咱们换一种方式来割:
不难发现净流量依然与网络流的流量相等, 依然是 $23$. 而这个割的容量则是 $23$.
为啥净流量一直是同样的呢? 咱们能够用下面这个不太严谨的证实感性理解一下
根据网络流的定义,只有源点 \(s\) 会产生流量,汇点 \(t\) 会接收流量。所以任意非 \(s\) 和 \(t\) 的点 \(u\) ,其净流量必定为 $0$,也便是$\sum f(u,v)=0$。而源点 \(s\) 的流量最终都会经过割 \((S,T)\) 的边到达汇点 \(t\),因此网络流的流 \(f\) 等于割的静流 \(f(S,T)\)。
也就是说任意一个割的净流必定都等于当前网络的流量.
而由于割的容量将全部可能的出边都计入了, 因此任意一个割的净流必定都小于等于这个割的容量. 而在全部的割中, 必定存在一个容量最小的割, 它限制了最大流的上界. 因而咱们获得一个结论: 对于任意一个流网络, 其最大流一定不大于其最小割.
然而这还不够, 咱们至关于只能用最大流算出最小割的一个下界. 咱们如何证实这个下界必定能取到呢?
最小割最大流定理的内容:
对于一个网络流图 \(G=(V,E)\),其中有源点 \(s\) 和汇点 \(t\),那么下面三个条件是等价的:
- 流 \(f\) 是图 \(G\) 的最大流
- 残量网络 \(G_f\) 不存在增广路
- 对于 \(G\) 的某一个割 \((S,T)\) ,此时流 \(f\) 的流量等于其容量
证实以下:
首先证实 $1\Rightarrow 2$:
増广路算法那的基础, 正确性显然, 不证了(咕咕咕
而后证实 $2\Rightarrow 3$:
假设残留网络 \(G_f\) 不存在增广路,因此在残留网络 \(G_f\) 中不存在路径从 \(s\) 到达 \(t\) 。咱们定义 \(S\) 集合为:当前残留网络中 \(s\) 可以到达的点。同时定义 \(T=V-S\)。 此时 \((S,T)\) 构成一个割 \((S,T)\) 。且对于任意的 \(u\in S,v\in T\),边 \((u,v)\) 一定满流。若边 \((u,v)\) 不满流,则残量网络中一定存在边 \((u,v)\),因此 \(s\) 能够到达 \(v\),与 \(v\) 属于 \(T\) 矛盾。 所以有 \(f(S,T)=\sum f(u,v)=\sum c(u,v)=C(S,T)\)。
最后证实 $3\Rightarrow 1$:
割的容量是流量的上界, 正确性显然.
因而, 图的最大流的流量等于最小割的容量.
就是开头那题
首先咱们发现不在最短路上的边都没卵用, 因而咱们把它们扔进垃圾桶.
剩下的边组成的图中求最小割.
有了最大流最小割定理, 直接跑一遍最大流就好辣~
这是在阿尔托利亚·潘德拉贡成为英灵前的事情,她正要去拔出石中剑成为亚瑟王,在这以前她要去收集一些宝石。
宝石排列在一个n*m的网格中,每一个网格中有一块价值为v(i,j)的宝石,阿尔托利亚·潘德拉贡能够选择本身的起点。
开始时刻为0秒。如下操做,每秒按顺序执行
1.在第i秒开始的时候,阿尔托利亚·潘德拉贡在方格(x,y)上,她能够拿走(x,y)中的宝石。
2.在偶数秒,阿尔托利亚·潘德拉贡周围四格的宝石会消失
3.若阿尔托利亚·潘德拉贡第i秒开始时在方格(x,y)上,则在第i+1秒能够当即移动到(x+1,y),(x,y+1),(x-1,y)或(x,y-1)上,也能够停留在(x,y)上。
求阿尔托利亚·潘德拉贡最多能够得到多少价值的宝石
首先有一个很是重要的套路: 对于网格图网络流来讲, 黑白染色是一个很重要的事情. 由于你须要选一部分点和 \(s\) 相连, 一部分点和 \(t\) 相连, 而后再在它们之间各类连边来表示贡献.
而后咱们来看题...
这出题人语文真棒
由于你能够任意停留或者行动并且行动时间不受限制, 其实意思就是选了一个点以后周围的点都不能选了...
这个时候咱们引入最小割建图的一个重要元素: 无穷边.
无穷边显然是不可能出如今最小割里的, 因而一条 \((u,v)\) 容量为 \(\infty\) 的边的意思就是: \((u,v)\) 必须连通, 不可割断.
同时因为咱们要求这个图上的一个割, 因此上面这句话等价于: 强制 \(s\verb|-| u\) 之间在最小割中被割断或者 \(v\verb|-|t\) 在最小割中被割断.
咱们再来看这个题. 咱们能够把问题从"咱们最多拿多少"转化为"咱们最少要扔掉多少". 因为相邻的点不能同时拿, 因此咱们在它们之间链接一条 \(\infty\) 边表明扔掉任意一个. 因此总的建图就是:
从 \(s\) 向全部白点连边, 从全部黑点向 \(t\) 连边, 容量均为点权. 相邻点之间从白点向黑点链接 \(\infty\) . 容易证实这张图的最小割值即为最少须要放弃的点的价值.
总和减去这个最小放弃值即为答案.
实际上上面的最小割求的就是二分图最小权值覆盖, 减出来的答案就是二分图的最大权独立集.
有 \(n\) 个正整数,须要从中选出一些数,使这些数的和最大。 若两个数 $a,b$同时知足如下条件,则 \(a,b\) 不能同时被选
- 存在正整数$c$,使 \(a^2+b^2=c^2\)
- \(\gcd(a,b)=1\)
首先 "知足必定条件则不能同时被选" 是一个典型的最小割问题, 计算舍弃掉的最小值便可.
可是网络流建图必需要在合适的地方放 \(s\) 和 \(t\) (因而全局最小割就成了个高端问题), 咋办?
首先咱们发现偶数和偶数之间不可能有限制条件, 由于它们的 \(\gcd\) 为 $2$, 不知足第二个条件.
接着咱们发现奇数和奇数之间不可能知足第一个条件. 为啥?
奇数的平方和在模 $4$ 意义下必定是 $2$, 而彻底平方数在模 $4$ 意义下必须是 $0/1$. 枚举 \([0,4)\) 之间的数字就证完了.
因此它是个二分图! 咱们就能够愉快地从 \(s\) 连边到奇数, 从偶数连边到 \(t\) , 中间同时知足条件的再连几条 \(\infty\) 边就行了...么?
emmmm...
题面漏了一句话:
\(n \leq 3000\)
然而这并非单位容量简单网络因此不适用 \(O(\sqrt{V}E)\) 的复杂度证实...
GG了?
咱们必需要知道, Dinic是一种敢写就会有奇迹的算法!
它A了.
A,B两个国家正在交战,其中A国的物资运输网中有N个中转站,M条单向道路。设其中第i (1≤i≤M)条道路链接了vi,ui两个中转站,那么中转站vi能够经过该道路到达ui中转站,若是切断这条道路,须要代价ci。如今B国想找出一个路径切断方案,使中转站s不能到达中转站t,而且切断路径的代价之和最小。 小可可一眼就看出,这是一个求最小割的问题。但爱思考的小可可并不局限于此。如今他对每条单向道路提出两个问题: 问题一:是否存在一个最小代价路径切断方案,其中该道路被切断? 问题二:是否对任何一个最小代价路径切断方案,都有该道路被切断? 如今请你回答这两个问题。
首先咱们为了求出这个最小割确定要先跑最大流, 跑完最大流以后的残量网络就是这题的突破口.
咱们分析一下这个残量网络有什么性质:
结论1很是显然, 最大流的话 \(s\) 和 \(t\) 直接不连通更不要说在同一SCC里了.
结论2的话, 若是将一条知足该限制的边容量增大, 那么 \(s\rightarrow t\) 从新连通, 因而就会增长最大流的流量, 也至关于增长了最小割的容量. 因此这条边一定会出如今最小割中.
结论3的话, 咱们把SCC缩到一个点里, 获得的新图就只有知足条件的满流边了. 缩点后的任意一个割显然就对应着原图的一个割, 因此这些满流边均可以出如今最小割中.
这三条结论有时候在最小割建图输出方案的时候会用到.
给定一个有向图, 顶点带权值. 你能够选中一些顶点, 要求当选中一个点 \(u\) 的时候, 若存在边 \(u\rightarrow v\) 则 \(v\) 也必须选中. 最大化选中的点的总权值.
选中一个点后能够推导出另外一个点也必须选中, 同时最优化一个值, 咱们考虑用最小割的 \(\infty\) 边来体现这一点.
咱们像刚刚王者之剑那道题同样将价值转化为代价. 这样当不选一个正权点时至关于付出 \(val_i\) 的代价, 选中一个负权点时至关于付出 \(-val_i\) 的代价.
咱们设割断后和 \(s\) 相连的点是被选中的, 和 \(t\) 相连的点是被扔掉的, 那么当一个正权点和 \(s\) 割断时要付出 \(val_i\) 的代价, 咱们从 \(s\) 连一条容量为 \(val_i\) 的边到这个点. 相似的, 当一个负权点和 \(t\) 割断时(等价于和 \(s\) 相连, 也就是被选中了)要付出 \(-val_i\) 的代价, 从这个点连一条容量为 \(-val_i\) 的边到 \(t\) 便可.
原图中的边不能割断, 因此咱们连 \(\infty\) 边.
最后用全部正权点的权值和减去最小割就是答案了.
通过千辛万苦小 A 获得了一块切糕,切糕的形状是长方体,小 A 打算拦腰将切糕切成两半分给小 B 。出于美观考虑,小 A 但愿切面能尽可能光滑且和谐。因而她找到你,但愿你能帮她找出最好的切割方案。 出于简便考虑,咱们将切糕视做一个长 \(P\) 、宽 \(Q\) 、高 \(R\) 的长方体点阵。咱们将位于第 \(z\) 层中第 \(x\) 行、第 \(y\) 列上 \((1 \le x \le P, 1 \le y \le Q, 1 \le z \le R)\) 的点称为 \((x,y,z)\),它有一个非负的不和谐值 \(v(x,y,z)\) 。一个合法的切面知足如下两个条件:
- 与每一个纵轴(一共有 \(P\times Q\) 个纵轴)有且仅有一个交点。即切面是一个函数 \(f(x,y)\),对于全部 $1 \le x \le P, 1 \le y \le Q$ ,咱们需指定一个切割点 \(f(x,y)\) ,且 $1 \le f(x,y) \le R$ 。
- 切面须要知足必定的光滑性要求,即相邻纵轴上的切割点不能相距太远。对于全部的 $1 \le x,x’ \le P$ 和 $1 \le y,y’ \le Q$ ,若 \(|x-x’|+|y-y’|=1\) ,则 $|f(x,y)-f(x’,y’)| \le D$∣ ,其中 \(D\) 是给定的一个非负整数。 可能有许多切面 \(f\) 知足上面的条件,小 A 但愿找出总的切割点上的不和谐值最小的那个,即 \(\sum\limits_{x,y}{v(x, y, f (x, y))}\) 最小。
给定一个立方体, 而后你要对于每个 \((x,y)\) 选择一个切割高度 \(f(x,y)\) , 而选中某个特定切点以后会产生必定的花费, 同时相邻两个切点的高度差不能超过 \(D\) , 求花费最小的切割方案.
题都告诉你要求一个花费最小的切割方案了固然要选择最小割啦
首先咱们不考虑高度差限制, 这样的话咱们从每一个位置开始挂一条长链, 链上边的权值大小表示割断对应位置的花费.
而后愉快地贪心最小割就行了啊~
然而如今考虑高度差限制.
这时候咱们再次使用 \(\infty\) 边来解决这个限制.
假设咱们建出了这样的两条链:
而后假设高度差限制是 $1$ , 则咱们割这样的两条边是不合法的:
回忆 \(\infty\) 边的做用: 强制令 \(s\verb|-|u\) 与 \(v\verb|-|t\) 中任选一个割断. 那么咱们能够这样建一条 \(\infty\) 边:
那么咱们发现, 这种状况变得不合法了: \(s\) 和 \(t\) 依然连通.
咱们发现, 只要上面那条链上割断的是 \((3,4)\), 那么 \((5,6)\) 一定不会出如今最小割中. 由于下面那条链里显然只会割掉 $6\verb|-|t$ 上的某一条边, 割两条边确定没有割一条边更优.
或者说, \(s\verb|-|u\) 割断的话, 对第二条链在这个高度上不起限制, 第二条链依然能够割断任意一条边.
若是 \(s\verb|-|u\) 未割断的话, 必定是 \(u\verb|-|t\) 割断了, 那么这条边就会强制 \(v\verb|-|t\) 割断, 那么 \(s\verb|-|v\) 割断必定不优因而不会出如今最小割中.
上限同理, 可是实际上若是全部方向都考虑的话就是换个方向的下限限制, 建边都同样就能够了.
有时候咱们会发现这样的一类问题:
公司有 \(m\) 个仓库和 \(n\) 个零售商店。第 \(i\) 个仓库有 \(a_i\) 个单位的货物;第 \(j\) 个零售商店须要 \(b_j\) 个单位的货物。货物供需平衡,即 \(\sum\limits_{i = 1} ^ m a_i = \sum\limits_{j = 1} ^ n b_j\) 。从第 \(i\) 个仓库运送每单位货物到第 \(j\) 个零售商店的费用为 \(c_{ij}\) 。试设计一个将仓库中全部货物运送到零售商店的运输方案,使总运输费用最少。
咱们看到"货物供需平衡"这个关键字其实就已经能够料想到这是个流问题了. 可是不一样的是它给每单位流量都增长了一个费用, 怎么办呢?
首先咱们须要意识到一个事情: 最小费用最大流, 是在最大流的基础上取最小费用, 因此咱们是必需要跑到最大流的(这一点也能够在构图中用来表示限制). 因而咱们伪装没有费用先算増广路.
对于一条増广路 \(p\) 来讲, 咱们设増广了 \(f\) 单位的流量, 则总花费为 \(\sum\limits_{i\in p} d_if\), 实际上就等于 \(f\sum\limits_{i\in p}d_i\), 也就等于把费用看作距离的路径长度乘以流量大小.
因此咱们继续沿用EK最大流的思路, 每次増广以费用为边权的最短路径便可.
咱们尝试扩展最大流时的反向边建法: 创建一个残量为 $0$ 的边. 费用呢? 最大流里面全部的边都至关因而单位边权的费用, 因此咱们能够伪装反向边的费用和正向边同样~
裆燃是假的辣!
费用流里求的流量在最终累加答案的时候都乘了一个费用系数, 因而此次咱们推流的时候不只要把流推走, 还要把贡献的费用减小. 因此反向边的权值实际上是正向边的相反数.
因而费用流中, 负权边不可避免. 因此咱们使用SPFA来求最短路.
它死了(划掉
Q: 为啥要用SPFA呢? 这玩意复杂度不是玄学么?
A: 如今有负权你不得不用了...SPFA复杂度虽然是玄学可是它仍是有一个 \(O(VE)\) 的科学上界的, 因此在负权图上跑SPFA也不失为一个很好的选择(固然也有更优秀可是有一些限制的Dijkstra费用流, 不过通常用不着...)
Q: 你这个费用流里既然会冒出负权来, 那要是推反向边的时候在残量网络里増广出负环了怎么破?
A: EK费用流的过程每次只増广最短路, 因此任意时刻増广出来的流必定都是最小费用流. 可是若是残量网络中存在负环, 那么咱们显然可让一部分流量改道流经这个负环来让费用减小, 这样就矛盾了. 因此必定不会増广出负环.
由于要把一次BFS寻找増广路换成SPFA+DFS, 因而复杂度上界从 \(O(E)\) 升高到 \(O(VE)\) , 其他的EK最大流复杂度分析理论上依然使用于此, 因此总时间复杂度上界为 \(O(V^2E^2)\).
这锅原本扔给chr了...然而他以为这很毒瘤就又丢回来了...
这个算法基于下面这个定理:
流量为 \(f\) 的流是最小费用流当且仅当对应的残量网络中不存在负费用增广圈。
感性证实:
若是在一个流网络中求出了一个最大流,但对于一条增广路上的某两个点之间有负权路,那么这个流必定不是最小费用最大流,由于咱们可让一部分流从这条最小费用路流过以减小费用,因此根据这个思想,能够先求出一个最大初始流,而后不断地经过负圈分流以减小费用,直到流网络中不存在负圈为止。关键在于负圈内全部边流量同时增长是不会改变总流量的,却会下降总费用。
因而咱们就能够直接先跑一遍最大流, 而后在残量网络里用 Bellman-Ford 找负环而后沿着负环増广一发就能够了. 固然若是用SPFA的话大概会跑得比 \(\varTheta(VE)\) 的 Bellman-Ford 要快点吧.
网上说按照必定顺序消圈的话复杂度是 \(O(VE^2\log V)\) 的...可是原本这不是个人锅因此并无仔细搞
具体实现找chr锔锅.
其实ZKW费用流和EK费用流的关系就跟Dinic和EK最大流的关系差很少...
就是加了个对全部符合 \((u,v)\) 在最短路上的边进行多路増广...
可是因为存在负权, 因此増广时可行的边组成的图并不像Dinic那样是分层图.
Dinic的BFS
部分直接改为SPFA求最短路就行了, 这个没啥好说的.
DFS
部分要注意一点, 由于可行边组成的图(如下简称"可行图")并不必定是DAG, 因而咱们可能能够从一个费用为负的边跑回原来的地方, 因而就会在一个 $0$ 环上转来转去死递归.
解决方案是加一个 vis
数组, 只要DFS到了这个点就打个标记, 同时在DFS的时候判断一下出边是否会跑到一个已经打了标记的点, 若是没有打上标记再DFS.
为了保证多路増广的优越性, 这个 vis
须要在回溯时撤销.
如下是 LOJ #102 最小费用流的AC代码
#include <bits/stdc++.h> const int MAXV=5e2+10; const int MAXE=1e5+10; const int INFI=0x7F7F7F7F; struct Edge{ int from; int to; int dis; int flow; Edge* rev; Edge* next; }; Edge E[MAXE]; Edge* head[MAXV]; Edge* top=E; int v; int e; int p,m,f,n,s; int dis[MAXV]; bool vis[MAXV]; bool SPFA(int,int); int DFS(int,int,int); void Insert(int,int,int,int); std::pair<int,int> Dinic(int,int); int main(){ scanf("%d%d",&v,&e); for(int i=0;i<e;i++){ int a,b,c,d; scanf("%d%d%d%d",&a,&b,&c,&d); Insert(a,b,d,c); } std::pair<int,int> ans=Dinic(1,v); printf("%d %d\n",ans.first,ans.second); return 0; } std::pair<int,int> Dinic(int s,int t){ std::pair<int,int> ans; while(SPFA(s,t)){ int flow=DFS(s,INFI,t); ans.first+=flow; ans.second+=flow*dis[t]; } return ans; } int DFS(int s,int flow,int t){ if(s==t||flow<=0) return flow; int rest=flow; vis[s]=true; for(Edge* i=head[s];i!=NULL;i=i->next){ if(i->flow>0&&dis[s]+i->dis==dis[i->to]&&(!vis[i->to])){ int k=DFS(i->to,std::min(rest,i->flow),t); rest-=k; i->flow-=k; i->rev->flow+=k; if(rest<=0) break; } } vis[s]=false; // 这里不太对头 return flow-rest; } bool SPFA(int s,int t){ memset(dis,0x7F,sizeof(dis)); std::queue<int> q; vis[s]=true; dis[s]=0; q.push(s); while(!q.empty()){ s=q.front(); for(Edge* i=head[s];i!=NULL;i=i->next){ if(i->flow>0&&dis[s]+i->dis<dis[i->to]){ dis[i->to]=dis[s]+i->dis; if(!vis[i->to]){ vis[i->to]=true; q.push(i->to); } } } q.pop(); vis[s]=false; } return dis[t]<INFI; } inline void Insert(int from,int to,int dis,int flow){ // printf("Insert %d -> %d : dis=%d flow=%d\n",from,to,dis,flow); top->from=from; top->to=to; top->dis=dis; top->flow=flow; top->rev=top+1; top->next=head[from]; head[from]=top++; top->from=to; top->to=from; top->dis=-dis; top->flow=0; top->rev=top-1; top->next=head[to]; head[to]=top++; }
你们发现个人代码里并无加当前弧优化. 为何?
刚刚说到了, ZKW费用流有一个sb特色就是可行图不是分层图. 那么就有可能有这种操做(下面只画可行图, 没有带权):
而后咱们DFS増广, 可能会遇到这样的东西:
咱们发现 \((4,2)\) 这条边到达了一个已经被标记过的点, 因而它不会产生流量贡献. 可是若是咱们加了当前弧优化, 咱们就会在之后访问到 $4$ 结点时跳过 \((4,2)\) 这条出弧, 因而咱们就否认掉了下面这种状况:
因而咱们DFS的过程就会提早认为当前已经阻塞, 返回进行新一轮SPFA. 因而咱们便进行了多余的SPFA操做, 这与多路増广"为了减小BFS次数"的出发点相悖, 同时也不能保证一个较低的时间复杂度.
实测结果的话, 使用LOJ #102的最后三个测试点, 发现不加当前弧优化时SPFA执行次数为 $400$ 左右, 而加入当前弧优化以后执行次数上升到了 $2700$ 次, 实际运行时间慢了一倍多.
证了证发现由于不能当前弧优化因此并不能保证比EK更优的时间复杂度...可是能够伪装它上界在非源非汇点度数比较小的时候能够有一个接近 \(O(VE^2)\) 的依然很松的上界. 实测在LOJ板子上比EK费用流要快, 网络流构图可能会更快一些.
还有就是关于 visit
标记的清空问题. 上面的板子里在DFS结束以后清空了 visit
标记, 这在一些状况下会让程序变快, 可是有些状况下可能会被卡成指数(捂脸)...建议你们DFS后不清空上面的 visit
标记而是在每次DFS前清空.
为啥要讲一下这玩意呢?
由于这玩意能够提升你的费用流暴力在费用流转贪心的题中获得的指望分数
整个的过程和EK/ZKW实际上是同样的, 不过此次把最短路换成Dijkstra了.
为啥能用Dijkstra呢? 它其实基于一个事实: 你已经求过原来的一个最短路了, 而最短路是知足三角形不等式的. 其次加入的反向边的权值都恰好是正向边的相反数.
咱们为每一个点$i$赋点权$h[i]$为$dis[i]$,并视每条链接$u,v$的边$i$的边权$w'[i]$为$w[i]+h[u]-h[v]$,因为对于任意两点$u,v$,有$h[u]-h[v]>=w[u][v]$,因此$w'[i]>=0$,这样一来新图上的$dis'[i]$就等于$dis[i]+h[S]-h[i]$(对于路径上除起点和终点之外的点$i$,其入边的$-h[i]\(与出边的\)+h[i]$抵消),因为每次跑最短路时$h[i]$都是不变的,因此求出了$dis'[i]$也就求出了$dis[i]$(\(dis[i]=dis'[i]-h[S]+h[i]\),其实很显然$h[S]=0$)
可是跑完以后须要加入反向边,原来的$h[i]$可能会不适用,因此咱们须要更新$h[i]$ 对于最短路上每一条链接$(u,v)$的边,显然有
\[dis'[u]+w'[u][v]=dis'[v] \]从而
\[dis'[u]+h[u]-h[v]+w[u][v]=dis'[v] \]\[(dis'[u]+h[u])-(dis'[v]+h[v])+w[u][v]=0 \]\[∵w[u][v]=-w[v][u] \]\[∴(dis'[v]+h[v])-(dis'[u]+h[u])+w[v][u]=0 \]因此咱们只要对于每一个点$i$将$h[i]$加上$dis'[i]$便可.
因此整个的实现就是: 先SPFA跑最短路算出 \(h\) , 而后在Dijkstra中把参考的边权加上一个$h[u]$再减去一个$h[v]$, 最后把计算出的 \(dis\) 累加上 \(h\) 就好了.
当你会了费用流以后你能作的题就会瞬间多一个数量级了...
小美要在南极进行科考,如今她要规划一下科考的路线。
地图上有 N 个地点,小美要从这 N 个地点中 x 坐标最小的地点 A,走到 x 坐标最大的地点 B,而后再走回地点 A。
请设计路线,使得小美能够考察全部的地点,而且在从 A 到 B 的路程中,考察的地点的 x 坐标老是上升的,在从 B 到 A 的过程当中,考察的地点的 x 坐标老是降低的。
求小美所须要走的最短距离(欧几里得距离)。
3 ⇐ N ⇐ 300
1 ⇐ x ⇐ 10000, 1 ⇐ y ⇐ 10000
乍一看好像是DP?
实际上网络流是能够作的
首先确定是要按横坐标排序, 而后咱们发现一去一回的方向并无什么卵用, 找到一个环和找到两条路径是等价的. 这样咱们能够发现, 每一个结点都必须选择两条边, 其中 \(x\) 最小的结点两条都是出边, 最大的结点两条都是入边, 其余结点一条入边一条出边. 咱们能够尝试分配这些入度和出度让总费用最小. 这样的话咱们能够获得这样的构图:
其中较粗的边容量为 $2$, 较细的边容量为$1$, \(s\) 连出的边和连向 \(t\) 的边费用为 $0$ , 实际结点间边的距离即为欧几里得距离.
一个餐厅在相继的 \(n\) 天里,天天需用的餐巾数不尽相同。假设第 \(i\) 天须要 \(r_i\) 块餐巾。餐厅能够购买新的餐巾,每块餐巾的费用为 \(P\) 分;或者把旧餐巾送到快洗部,洗一块需 \(M\) 天,其费用为 \(F\) 分;或者送到慢洗部,洗一块需 \(N\) 天,其费用为 \(S\) 分(\(S < F\))。
天天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。可是天天洗好的餐巾和购买的新餐巾数之和,要知足当天的需求量。
试设计一个算法为餐厅合理地安排好 \(n\) 天中餐巾使用计划,使总的花费最小。
这道题使用了另外一个费用流套路: 强行补充流量.
首先按照以往的套路, 咱们会选择用一单位流量来表示一块餐巾的整个生命周期: 从被购买到被使用再到被清洗最后被丢弃.
因而咱们从 \(s\) 链接容量为 \(\infty\) 费用为购买餐巾单价的边到天天的决策点, 而后再乱搞?
然而这题直接这么建模会GG.
注意最小费用最大流是在最大流基础上最小费. 若是餐巾量和流量相等的话那可就变成买的餐巾越多越好了.
接着咱们发现这题的最大难点在于: 洗了以后的餐巾能够再用.
因而咱们再也不用一单位流量流经的边来表示花费: 咱们用每一单位流量表示一个餐巾被用了一次. 而这个一单位流量的来源则用来表达这一份餐巾的花费, 无论是买的仍是洗的仍是从地上捡的. 因而咱们就能够用最大流这个限制来强制天天的餐巾必须够用.
其次, 由于天天都会产生必定量的脏餐巾, 咱们直接从源点 $0$ 费用把这些流量送达指定时间段日期以后的决策点来决定是否要洗. 由于网络流有流守恒的限制, 咱们能够像 "抽水" 同样来控制之前的决策.
可是直接连边给之后的决策点仍是会GG. 由于咱们发现由于转运的过程不能增长费用, 因而咱们直接 $0$ 费用把 \(s\) 连到了 \(t\).
若是咱们直接从 \(s\) 连一些费用为快/慢洗的边的话又没法控制它们的总流量了(你总不能洗出来的比当天用的还多吧)
这个时候咱们按照套路选择拆点. 咱们拆一些新的点负责把洗出来的脏餐巾流量转运到后面, 从 \(s\) 链接一条容量为当天餐巾用量的边到这个 tRNA 点, 而后从这个 tRNA 点出发链接两条费用为洗餐巾花费的边到当天快洗/慢洗洗完那天的决策点.
不过由于洗完的餐巾之后还能用, 为了体现这一点, 天天的决策点还应该向次日连一条 $0$ 费用 \(\infty\) 容量的边.
因而总的建图就是长这样的:
公司有 \(n\) 个沿铁路运输线环形排列的仓库,每一个仓库存储的货物数量不等。如何用最少搬运量可使 \(n\) 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
注1: 搬运量即为货物量与搬运距离之积.
注2: (大概) 保证货物数量的总和是 \(n\) 的倍数.
这道题建图其实比较直观.
首先求出目标货物数量(也就是平均数), 而后咱们发现, 对于 \(x_i>\bar{x}\), 它必定须要向外运输, 咱们从 \(s\) 引一条流量为 \(x_i - \bar x\) 的边到这个点. 而对于 \(x_i < \bar x\), 它必定是要接受货物的. 因而咱们引一条流量为 \(\bar x - x_i\) 的边到 \(t\) , 最后把相邻的仓库之间都连一条费用为 $1$ 容量为 \(\infty\) 的边跑费用流就能够了.
给定实直线 \(L\) 上 \(n\) 个开区间组成的集合 \(I\),和一个正整数 \(k\),试设计一个算法,从开区间集合 \(I\) 中选取出开区间集合 \(S\in I\),使得在实直线 \(L\) 的任何一点 \(x\),\(S\) 中包含点 \(x\) 的开区间个数不超过 \(k\) 。且 \(∑\limits_{z\in S}|z|\) 达到最大。这样的集合 \(S\) 称为开区间集合 \(I\) 的最长 \(k\) 可重区间集。 \(∑\limits_{z∈S}|z|\) 称为最长 \(k\) 可重区间集的长度。 对于给定的开区间集合 \(I\) 和正整数 \(k\),计算开区间集合 \(I\) 的最长 \(k\) 可重区间集的长度。
首先由于是实数因此得离散化...这可真蠢...
而后因为这是开区间, 咱们不须要考虑端点覆盖的次数, 因而咱们能够对于每一个区间, 从左端点到右端点连一条容量为 $1$ , 费用为区间价值 (也就是长度) 的边, 而后从源点到第一个点/从第 \(i\) 个点到第 \(i+1\) 个点/从最后一个点到汇点都连一条容量为 \(k\) 费用为 $0$ 的边, 跑最大费用最大流便可.
撕烤这样为啥是对的?
每一个点最多覆盖 \(k\) 次, 而一次覆盖至关于一次分流, 最多分流 \(k\) 次(由于你并不能搞出负流来).
这个用分流次数来表示限制的思想有时候也会用到.
给定一个流网络, 每条边 \((u,v)\) 有一个容量 \(c\) 和一个费用系数 \(w\) . 当边 \((u,v)\) 上加载了 \(f\) 单位流量的时候, 产生的花费为 \(f^2w\) . 求最小费用最大流.
\(c\leq 100\)
题意很是明确, 可是这费用并非线性的...
这题用到了费用流建图的一个技巧: 拆费用. 咱们把一条 \((u,v)\) 的容量为 \(c\) 的平方费用边拆成 \(c\) 条容量为 $1$ 的线性费用边. 就像这样:
第 \(i\) 条边的费用正好是平方费用边加载上第 \(i\) 单位流量的时候产生的费用贡献.
由于 \(c\) 并不大, 因此直接按照上面方法暴力建边就能够了.