零、目錄html
I、網絡流基礎算法
II、網絡流進階之轉換對偶圖網絡
III、網絡流進階之費用流優化
1、前言spa
本文爲上一篇文章《網絡流基礎》之續集,一樣3年前已有一篇文章講解轉換對偶圖,這裏再次爲其翻新一次,但願可以更好理解。code
2、最小割htm
講網絡流不得不提一個概念——最小割。便於理解,上一篇文章並無將其攪和進來。最小割是什麼呢?如今要求割斷部分路徑上的流量,使從源點沒有任何流量能夠到達匯點,而截取的流量最小值即最小割。咱們再次拿出上次的模型:blog
首先從1至4最直接的20流量必然須要截掉;從1至2理應截取40,但因爲2-3-4路徑上的最大流僅爲10,加上2-4流量爲20,故只需截取30;總計50流量。隊列
看着看着就以爲有意思了——對於任意一條路徑,其可以流通的流量最大值即是咱們須要割掉的流量最小值,即最大流=最小割。get
這裏說起最小割的概念,可以更好的理解接下來的內容——轉換對偶圖。先看一道例題。
3、例題
首先,裸網絡流的正確性毋庸置疑,此處再也不贅述,由於數據並無容許這種無腦的方式經過,因此咱們如今帶着腦子想一個更好的辦法。
4、轉換對偶圖
每一次狼的派遣其本質其割流。本題雖不是傳統網格圖,但一樣能夠看做一種特殊的三角網格圖。目的一樣是求最小割,咱們來先隨手割一刀看是什麼效果——
如圖爲一種非最小割方案,須要割掉33流量。咱們發現,當且僅當全部割線將圖劃分紅兩部分時,源點沒有流量流向匯點。瞭解這一項規律後,知足最小割的方案能夠輕易獲得——將圖中直接與匯點相連的三條邊割掉,能夠最小割爲3 + 5 + 6 = 14,一樣將圖割成兩部分。
如今,咱們可否將這張圖進行必定改進——對於每一次割,能夠理解爲走過一條連通這兩邊兩側區域的邊,其權值即流量,而咱們求的最小割即最小權值。因此如今咱們將原圖轉換一下——將每一塊區域看做一點,兩個區域之間的邊看做兩點之間相連的邊,稱之爲對偶圖。
看起來大功告成,不過咱們忽視了兩個更大的區域。全部邊界彷佛沒法處理?這時咱們將全部左下方的邊界與一個點視做相連,此點稱之爲超級源點;同理將右上方區域視做超級匯點。
這樣咱們每一次求最小割,其實本質是在新的對偶圖上跑最短路,起點爲超級源點,終點爲超級匯點。綜上,上述方案即爲:
5、代碼
要注意的是,N/M <=1000的條件下,其對偶圖最多會存在6*10^6個節點,Dijkstra算法時間複雜度爲O(n^2),顯然沒法經過;起初我嘗試了SPFA算法,但因爲其複雜度的不穩定,第10個點TLE了;最終修改爲了優先隊列優化的Dijkstra算法,其複雜度爲O(nlogn)。
#include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; #define MAXN 6000005 #define INF 0x3f3f3f3f int n, m, t, h[MAXN], dis[MAXN], w, o; struct Edge { int v, next, w; } e[MAXN]; struct Node { int n, w; }; struct cmp { bool operator () (Node a, Node b) { return a.w > b.w; } }; priority_queue <Node, vector<Node>, cmp> Q; void add(int u, int v, int w) { o++, e[o] = (Edge) {v, h[u], w}, h[u] = o; o++, e[o] = (Edge) {u, h[v], w}, h[v] = o; } void init() { scanf("%d %d", &n, &m), t = (n - 1) * (m - 1) * 2 + 1; memset(h, -1, sizeof(h)), memset(dis, INF, sizeof(dis)); for (int i = 1, tot = 2; i <= n; i++) for (int j = 1; j <= m - 1; j++, tot += 2) { int u = i == 1 ? t : tot - m * 2 + 1, v = i == n ? 0 : tot; scanf("%d", &w), add(u, v, w); } for (int i = 1, tot = 1; i <= n - 1; i++) { for (int j = 1; j <= m - 1; j++, tot += 2) { int u = j == 1 ? 0 : tot - 1; scanf("%d", &w), add(u, tot, w); } scanf("%d", &w), add(tot - 1, t, w); } for (int i = 1, tot = 1; i <= n - 1; i++) for (int j = 1; j <= m - 1; j++, tot += 2) scanf("%d", &w), add(tot, tot + 1, w); } void work() { Q.push((Node) {0, 0}), dis[0] = 0; while (!Q.empty()) { Node o = Q.top(); for (int x = h[o.n]; x != -1; x = e[x].next) { int v = e[x].v; if (dis[v] > dis[o.n] + e[x].w) dis[v] = dis[o.n] + e[x].w, Q.push((Node) {v, dis[v]}); } Q.pop(); } } int main() { init(); work(); printf("%d", dis[t]); return 0; }