算法学习:费用流
【前置知識】
最大流
?
【問題描述】
給定一個圖,圖上的邊每條邊有流量,流量有費用,
求在流量為 F 時費用的最小值
?
?
【分析思路】
求費用最小,我們很容易想到求最短路徑,我們只需要將費用看作代價然后求最短路不久能求出來最小費用了嘛
但是,問題來了
我們又如何能夠在求最小費用的同時,滿足流量最終為F的條件
在最大流中提到,每次增廣都是在殘余網絡中通過詢問 s 至 t 的殘余網絡進行增廣
所以,我們每次在殘余網絡上求最短路就可以了
這里注意,殘余網絡中的反向邊的費用應該時原邊費用的相反數
這樣在計算的時候,才能保證過程是可逆的
【證明】
在殘余網絡上求最短路是最小費用
證:
1? 設有? f 為以以上方式求出的結果, 設? f ’ 和 f 流量相同,但是費用更少
因為? f ‘ - f 的流量流入流出為 0,所以他是成環的
又因為 f ’ - f 的費用是負的,所以在這些環中,存在負環
結論:f 是最小費用流? ?<====>? 殘余網絡中沒有負環
(因為負環顯而易見的能夠通過轉圈減少費用)
2??對于流量為 0 的流 f0 ,其殘余網絡為原圖,原圖沒有負環,則f0 就是最小費用流
設流量為i 的流 fi 是最小流,并且下一步我們求得流量為 i + 1的流 f[i+1],按照方法,f[ i + 1 ] - f [ i ]就是 f[ i ]對應的參余網絡中 s 到 t 的最短路
假設 f [ i + 1 ] ' 的費用比 f [ i + 1 ]還小,則 f [ i + 1 ] '- f [ i ] 的費用比f[ i + 1 ] - f [ i ] 還小,所以f [ i + 1 ] '- f [ i ] 中有負環
所以這與假設矛盾,于是可以證明這種方法是正確的
?
?
?
【代碼】
(基本上就是在最大流的基礎上稍微改進了下)
// luogu-judger-enable-o2 #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #include<cstring> #define ll long long using namespace std; const int MAXN = 50010; const int MAXM = 100010; const ll INF = (1ll << 62) - 1; typedef pair<int, int> P; struct note {int to;int nt;int rev;ll cost;ll cal; }; struct edge {note arr[MAXM];int st[MAXN];ll dis[MAXN];ll h[MAXN];int cur[MAXN];int depth[MAXN];int pre[MAXN];int pree[MAXN];int top;int n, m, s, t;edge(){memset(st, -1, sizeof(st));memset(depth, -1, sizeof(depth));memset(dis, -1, sizeof(dis));top = 0;}void read(){top = 0;scanf("%d%d%d%d", &n, &m, &s, &t);for (int i = 1; i <= m; i++){int x, y;ll z,c;scanf("%d%d%lld%lld", &x, &y, &z,&c);add(x, y, z,c);}}bool dep(){queue<int> q;q.push(s);memset(depth, -1, sizeof(depth));depth[s] = 0;while (!q.empty()){int v = q.front(); q.pop();for (int i = st[v]; i != -1; i = arr[i].nt){int to = arr[i].to;if (!arr[i].cal)continue;if (depth[to] != -1)continue;depth[to] = depth[v] + 1;q.push(to);}}return (depth[t] != -1);}void add(int x, int y, ll z,ll c){top++; arr[top] = { y,st[x],top + 1,c,z }; st[x] = top;top++; arr[top] = { x,st[y],top - 1,-c,0 }; st[y] = top;}ll dfs(int now, ll val){if (now == t || !val)return val;ll flow = 0;for (int& i = cur[now]; i != -1; i = arr[i].nt){int to = arr[i].to;if (depth[to] != depth[now] + 1)continue;ll f = dfs(to, min(arr[i].cal, val));if (!f || !arr[i].cal)continue;flow += f;arr[i].cal -= f;arr[arr[i].rev].cal += f;val -= f;if (!val)return flow;}return flow;}ll dinic(){ll flow = 0;ll f;while (dep()){for (int i = 1; i <= n; i++)cur[i] = st[i];while (f = dfs(s, INF))flow += f;}return flow;}ll min_cost(ll f){ll res = 0;while (f > 0){fill(dis, dis + 1 + n, INF);priority_queue<P, vector<P>, greater<P>> q;dis[s] = 0;q.push(P(0, s));while (!q.empty()){P p = q.top(); q.pop();int v = p.second;if (dis[v] < p.first) continue;for (int i = st[v]; i != -1; i = arr[i].nt){note &e = arr[i];if (e.cal > 0 && dis[e.to] > dis[v] + e.cost + h[v] - h[e.to]){dis[e.to] = dis[v] + e.cost + h[v] - h[e.to];pre[e.to] = v;pree[e.to] = i;q.push(P(dis[e.to],e.to));}}}if (dis[t] == INF) return -1;for (int i = 0; i <= n; i++)h[i] += dis[i];ll d = f;//printf("2\n");for (int i = t; i != s; i = pre[i]){d = min(d, arr[pree[i]].cal);}f -= d;res += d * h[t];for (int i = t; i != s; i = pre[i]){arr[pree[i]].cal -= d;;int rev = arr[pree[i]].rev;arr[rev].cal += d;}//printf("3\n"); }return res;} }; edge road; edge tmp; int main() {int T;road.read();tmp = road;ll f = tmp.dinic();printf("%lld ", f);printf("%lld", road.min_cost(f));return 0; } View Code?
注意:因為求最大流之后網絡會發生變化,所以需要把最開始的原圖記錄下來
? ?費用流的求取也要在原圖上進行
?
轉載于:https://www.cnblogs.com/rentu/p/11323530.html
總結
- 上一篇: 杭电多校(六)2019.08.07--暑
- 下一篇: 算法学习:AC自动机