费用流
费用流
例题
P2517 [HAOI2010] 订货
考虑将月份建为点,源点向月份连接一条容量为 \(\infty\),单位费用为 \(0\) 的边,相邻月份之间连接一条容量为 \(S\),单位费用为 \(m\) 的边, 每个月份再向汇点连接一条容量为 \(u_i\),费用为 \(0\) 的边,然后这个图的最小费用最大流的最小费用就是最终的答案。
解释:为什么要保证是最大流?因为我们要满足每个月的需求,而且每个月进货的限制是没有要求的,所以最后的最大流一定就是月份向汇点连接的边的容量之和,所以这么建图就一定是对的,即最后最小费用就是最低成本。
POJ2516 Minimum Cost
考虑对于每个种类 \(k\) 都跑一次费用流,建图很显然就按照普通方法建就可以,然后加起来就完了。
2566 -- 【模拟试题】炸弹拆除
用 log 转乘为加后,变为最大费用最大流,直接求解即可。
51-nod 1338 找格子
板。
P4016 负载平衡问题
前置芝士:网络流
极其推荐这位神仙的 网络流日报,不会的同学可以去学习一下。
学习完成之后再做一下 最小费用最大流 的模板就可以去切掉这道题了。
本题讲解
如果您认真学习网络流并对网络流有初步了解,就会知道网络流题目主要考察的是 建图能力 ,就是您把图建出来就能切掉。
第一步,确认起点与终点。
我们发现,这些箱子都是一样的,没有突出的点,所以我们就要创 超级起点和超级终点 ,通俗点说就是把起点设为 0 ,重点设为 n+1 就行了。
第二步,把权值连上:
通过实际意义可得,从起点忘每个仓库连一条 容量为库存,费用为0 的边,就可以很容易地把库存送到每个仓库里。
参考代码:
for(int i=1;i<=n;i++)
{
c[i]=read();
sum+=c[i];
add(s,i,c[i],0);
add(i,s,0,0);
}
第三步,仓库之间连线:
由于每个仓库都可以往 两遍相邻的仓库 送货,就是把每两个仓库之间连一条无向边,容量为inf,费用为1,这样就可以进行分配了。
注意:1 与 n 之间的连线要特殊处理一下
参考代码:
for(int i=1;i<n;i++)
{
add(i,i+1,inf,1);
add(i+1,i,0,-1);
add(i+1,i,inf,1);
add(i,i+1,0,-1);
}
add(1,n,inf,1);
add(n,1,0,-1);
add(n,1,inf,1);
add(1,n,0,-1);
第四步,仓库与终点连线。
把每个仓库向终点连一条 容量为平均数,费用为0 的边,可以将每个仓库的容量 强制限制到平均数。又因为最小费用最大流求的是 在到达终点数量最多时 的最小费用,就可以顺理成章地求出结果。
平均数可以在输入中求解,我用 sum 表示了。
参考代码:
for(int i=1;i<=n;i++)
{
add(i,t,sum,0);
add(t,i,0,0);
}
UVA11613 Acme Corporation
注意到题目中“价格”、“容量”的定义和费用流中的相同。考虑费用流的方法。
建图的方式比较平凡。只需要将每个月份拆成“生产”和“售卖”两个点,从源点向生产点根据生产限制连边,从售卖点向汇点根据售卖限制连边。而在内部,我们将第 \(i\) 月的“生产”与它之后保质期内月份的“售卖”连边。这样,从源点到汇点就能表示原题的限制。
但如果你这样跑一次,样例就会输出 -5 ——居然亏钱了。
仔细一想就会知道,费用流要先满足最大流,但其实,我们不需要卖那么多,只要让利润最大就好。怎么办呢?
既然费用流要先满足最大流,能不能对图的结构做一些修改,使得无论卖多少矿物,流的大小都一样呢?
设原先的源点汇点是 \(s\)、\(t\),我们新建两个点 \(s'\)、\(t'\),从 \(s' \to s\),\(s \to t\),\(t \to t'\) 各连一条容量无穷大,费用为 0 的边。这样计算 \(s'\) 到 \(t'\) 的费用流,无论月份结点流了多少流量,由于 \(s \to t\) 边的存在,总流量都是无穷大,而费用就是月份结点的费用。这样就实现了最小费用任意流。
其它的题解都是在费用流中提前终止实现的,正确性不显然,这个方法会更无脑一点。
#include <bits/stdc++.h>
using namespace std;
template <class T> void read(T &r) {
r = 0; int ch = getchar(), f = 0;
while (!isdigit(ch)) f ^= (ch == '-'), ch = getchar();
while (isdigit(ch)) (r *= 10) += ch - 48, ch = getchar();
if (f) r = -r;
}
template <class T, class... Ts> void read(T &r, Ts &...rs) { read(r); read(rs...); }
#define in(u) u
#define out(u) u + n
const int maxm = 5e2 + 5, maxn = 1e3 + 5;
const long long inf = 0x3f3f3f3f3f3f3f3f;
int n, cnt = 1, s, t, ss, tt;
struct Edge {
int v, nxt;
long long w, c;
} e[maxm];
int head[maxn], cur[maxn], ti[maxn];
long long dis[maxn], c1[maxn], w1[maxn], c2[maxn], w2[maxn], cost, k;
bool vis[maxn];
void add(int u, int v, long long w, long long c) {
e[++cnt].nxt = head[u]; head[u] = cnt;
e[cnt].v = v, e[cnt].w = w, e[cnt].c = c;
}
bool spfa() {
deque<int> dq; dq.emplace_back(t);
memset(dis, 0x3f, sizeof dis); memset(vis, 0, sizeof vis);
vis[t] = true; dis[t] = 0; while (!dq.empty()) {
int u = dq.front(); dq.pop_front(); vis[u] = false;
for (int i = head[u]; i; i = e[i].nxt) {
if (e[i ^ 1].w && dis[e[i].v] > dis[u] - e[i].c) {
dis[e[i].v] = dis[u] - e[i].c;
if (!vis[e[i].v]) {
if (!dq.empty() && dis[e[i].v] < dis[dq.front()]) dq.emplace_front(e[i].v);
else dq.emplace_back(e[i].v);
vis[e[i].v] = true;
}
}
}
} return dis[s] != inf;
}
long long dfs(int u = s, long long flow = inf) {
vis[u] = true; if (!flow || u == t) return flow;
long long rest = flow; for (int i = cur[u]; i; i = e[i].nxt) {
cur[u] = i; if (!vis[e[i].v] && dis[e[i].v] == dis[u] - e[i].c && e[i].w) {
long long tmp = dfs(e[i].v, min(rest, e[i].w));
e[i].w -= tmp, e[i ^ 1].w += tmp;
if (!(rest -= tmp)) break;
}
} return flow - rest;
}
void mcmf() {
cost = 0; while (spfa()) do {
memcpy(cur, head, sizeof cur); memset(vis, 0, sizeof vis);
cost += dis[s] * dfs();
} while (vis[t]);
}
int main() {
int _; read(_);
for (int __ = 1; __ <= _; __++) {
read(n, k); cnt = 1; memset(head, 0, sizeof head); s = 0, t = 2 * n + 1, ss = 2 * n + 2, tt = 2 * n + 3;
for (int i = 1; i <= n; i++) read(c1[i], w1[i], c2[i], w2[i], ti[i]);
for (int i = 1; i <= n; i++) {
add(ss, in(i), w1[i], c1[i]);
add(in(i), ss, 0, -c1[i]);
add(out(i), tt, w2[i], -c2[i]);
add(tt, out(i), 0, c2[i]);
for (int j = i; j <= min(n, i + ti[i]); j++) {
add(in(i), out(j), inf, k * (j - i));
add(out(j), in(i), 0, -k * (j - i));
}
}
add(ss, tt, inf, 0); add(tt, ss, 0, 0);
add(s, ss, inf, 0); add(ss, s, 0, 0);
add(tt, t, inf, 0); add(t, tt, 0, 0);
mcmf();printf("Case %d: %lld\n", __, -cost);
}
return 0;
}
P4068 [SDOI2016] 数字配对
一道非常好的网络流题
思路 二分+费用流
- 首先我们考虑建图
这其实与其他题解差不多,统计每个数其质因数指数之和,记录为 \(tot_i\) ,若一个数能整除另一个数且 \(tot\) 之差为一则连边,建立超级源点 s,与汇点 t,对于每个奇数的 \(tot_i\) 与 s 连边,偶数与t连边。
- 然后我们来想如何求在获得的价值总和不小于0的前提下,进行最多配对次数。
因为易证取的费用小于 0 次数之前的都大于等于0之后的都小于0。
所以可以二分流量。
在建一个超超级源点 ss,和汇点 tt,ss 与 s连一条g的边,t 与 tt 连一条 g 的边( g 为流量)。
注要开 long long。
P2153 [SDOI2009] 晨跑
首先我们发现需要求路程最短,天数尽量长。那么我们可以考虑最小费用最大流,其中路程为费用,天数为流量。
由于每个点只能被访问 \(1\) 次,那么我们进行拆点,将 \(i\) 拆成 \(i_1\) 和 \(i_2\),其中 \(i_1\) 和 \(i_2\) 之间连边 \((i_1,i_2,1,0)\)(容量为 \(1\),费用为 \(0\)),对于有向图的每条边 \((u,v,w)\) 连边 \((u_2,v_1,1,w)\) 和其反向边 \((v_1,u_2,0,-w)\)。
又因为 \(1\) 和 \(n\) 可以多次经过,那么源点和汇点分别为 \(s_2\) 和 \(t_1\),然后直接跑网络流即可。
时间复杂度:\(O(nmf)\)。
P4452 [国家集训队] 航班安排
好像这道题并没有其他两篇题解说的那么简单吧???或者是我太菜了
考虑以请求为点进行建图,对每个请求进行拆点,拆点后两个点之间连价值为 \(c\),流量为 \(1\) 的边,代表着一个请求只能执行一次。
然后我们考虑时间限制:
对于一个请求,如果 \(0\) 时刻可以从 \(0\) 机场飞到该请求的起点机场,那么源点向该请求连价值为(\(-\)飞行费用),流量为 \(INF\) 的边,同理,若一个请求的结束时间,加上它的结束机场飞回 \(0\) 的时间小于等于总的时间限制,该请求向汇点连边。
但是每次执行完一个请求并未规定一定要飞回 \(0\) 机场,也可以飞去其他请求的起点机场,所以两两枚举请求,如果满足时间条件也进行连边。
最后考虑有 \(k\) 架飞机,所以再建一个源点,向原来的源点连费用为\(0\),流量为\(k\)的边即可。
最后跑最大费用最大流。
最后注意飞机可以一直留在 \(0\) 号机场。
P2053 [SCOI2007] 修车
古希腊哲学家赫拉克利曾说:“人不能两次踏进同一条河流。”这句话承认了辩证唯物主义中绝对运动与相对静止的统一的观点,是正确的。
人不能两次踏进同一条河流,因为你第一次踏进的河流和第二次踏进的河流已经不是同一个河流了(水流走了)。同样地,对于一个修车工人而言,修第一辆车的他和修第二辆车的他不是同一个人。
所以本题的错误建图方式是,建一个二分图,左边是车,右边是工人,连边跑最小费用流。这样建图体现的是形而上学的不变论,是错误的。
说人话:对于每个工人而言,他在修第\(k\)辆车的时候,之前已经修了\(k-1\)辆车,所以对于不同的\(k\),对应的客人等待的时间是不同的,因此我们需要将每个工人拆成\(n\)个点,分别表示修第几辆车的他。
但是你会发现这样做的话连边时的边权比较困难。所以我们需要做一个推导。假设某个工人一共修了\(K\)辆车,花的时间分别为\(T_1,T_2,\cdots,T_K\),那么当他修第一辆车时,后面的\(K-1\)个人必须等着,同时第\(1\)个人也要等他修好,所以总共等待时间为\(K\times T_1\);接下来修第二辆车时同理,有\(K-1\)个人在等他修,所以时间是\((K-1)\times T_2\),以此类推,总等待时间即为
不难发现,他倒数第\(i\)个修的车对应的客人总共要贡献\(T_i\times i\)的等待时间。于是有了这一步转化,我们可以给出最终的建图方案了:将每个工人拆成\(n\)个点,分别表示修倒数第几辆车的他;如果第\(j\)个工人修第\(i\)辆车要花\(T(i,j)\)的时间,我们枚举\(k=1,\cdots,n\),从第\(i\)辆车向第\(j\)个工人的第\(k\)个点连边,容量为\(1\),费用为\(k\times T(i,j)\)。然后跑最小费用最大流,最后总费用除以\(n\)即可。

浙公网安备 33010602011771号