[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\) 並不大, 因此直接按照上面方法暴力建邊就能夠了.