算法学习:费用流
Posted rentu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法学习:费用流相关的知识,希望对你有一定的参考价值。
【前置知识】
【问题描述】
给定一个图,图上的边每条边有流量,流量有费用,
求在流量为 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;
注意:因为求最大流之后网络会发生变化,所以需要把最开始的原图记录下来
费用流的求取也要在原图上进行
以上是关于算法学习:费用流的主要内容,如果未能解决你的问题,请参考以下文章