题意
共 nn 天,每天需要 块干净的纸巾。纸巾用完一次就脏了,可以选择清洗后使用或不再使用。
已知新买纸巾的价格为 pp ,花 天快洗纸巾的价格为 ff ,花 天慢洗纸巾的价格为 ss 。
求最小花费。
数据范围
,Ri≤107Ri≤107,p,f,s≤104p,f,s≤104 。
题解
考虑网络流算法。
第一天为源点 SS ,最后一天为汇点 ,纸巾为流量,价格为费用。
通过简单贪心可以得到每天的干净纸巾刚好为所需要的数量时,总花费一定最小。
由此问题转化为求建图后跑满流的情况下的最小费用。
最小费用最大流。
考虑如何使得满足每天恰好使用 RiRi 块纸巾。
将每天拆成两个点,即使用前 XiXi 和使用后 YiYi。
建图方法如下((c,f)(c,f) 即费用为 cc、流量为 的边):
- SS 与 连接一条(0,Vi)(0,Vi) 的边,即第 ii 天纸巾需要的数量;
- 与 TT 连接一条 的边,即第 ii 天使用的纸巾的数量;
- 与 YiYi 连接一条 (p,inf)(p,inf) 的边,即第 ii 天购买新纸巾,花费为 ;
- XiXi 与 Xi+1Xi+1 连接一条 (0,inf)(0,inf) 的边,即第 ii 天没用完的纸巾留给第 天,没有费用;
- XiXi 与 Yi+mYi+m 连接一条 (f,inf)(f,inf) 的边,即第 ii 天快洗后的纸巾第 天拿到干净纸巾,花费为 ff;
- 与 Yi+nYi+n 连接一条 (s,inf)(s,inf) 的边,即第 ii 天慢洗后的纸巾第 天拿到干净纸巾,花费为 ss。
由于所有与源点 的所有边都与每一个 XiXi 相连,由此保证了每天都有 RiRi 的流量流入,即 RiRi 块新纸巾。
由于所有与汇点 TT 的所有边都与 相连,由此限制了每天必须有 RiRi 的流量流出(因为是跑最大流)。
可以发现所有所有与 TT 相连的边 ,边权和 ∑ef=∑Ri∑ef=∑Ri,保证了最大流为所需纸巾数之和,即上文说道的通过贪心得到最小费用的情况。
至于购买新纸巾,快洗和慢洗就都不难理解了。
参考代码
// Copyright 2018, Skqliao
// 最小费用最大流
#include <bits/stdc++.h>
#define rg register
#define rep(i, l, r) for (rg int i = (l), _##i##_ = (r); i < _##i##_; ++i)
#define rof(i, l, r) for (rg int i = (l) - 1, _##i##_ = (r); i >= _##i##_; --i)
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) static_cast<int>((x).size())
typedef long long ll;
namespace io {
#ifndef ONLINE_JUDGE
char gc() {
return getchar();
}
template <class T> inline T gt() {
register T x;
std::cin >> x;
return x;
}
template <typename T> inline void pt(T x, const char c = '\n') {
std::cout << x << c;
}
void fflush() {}
#else
const int MAXSIZE = 1 << 22;
inline char gc() {
static char In[MAXSIZE], *at = In, *en = In;
if (at == en) {
en = (at = In) + fread(In, 1, MAXSIZE, stdin);
}
return at == en ? EOF : *at++;
}
template <class T> inline T gt() {
register char c;
while (c = gc(), !isdigit(c) && c != '-') {}
register bool f = c == '-';
register T x = f ? 0 : c - '0';
for (c = gc(); isdigit(c); c = gc()) {
x = x * 10 + c - '0';
}
return f ? -x : x;
}
char Out[MAXSIZE], *cur = Out;
template <typename T> inline void pt(T x, char c = '\n') {
static int S[20], *top;
top = S;
if (x < 0) {
*cur++ = '-', x = -x;
}
do {
*++top = x % 10, x /= 10;
} while (x);
while (top != S) {
*cur++ = *top-- + '0';
}
*cur++ = c;
}
void fflush() {
fwrite(Out, 1, cur - Out, stdout);
cur = Out;
}
#endif
} // namespace io
namespace mcmf {
const int MAXN = 4000 + 5;
const int MAXM = MAXN * 3;
const ll INF = LLONG_MAX;
struct Edge {
int v, nxt;
ll c, f;
} E[MAXM << 1];
int S, T;
int Path[MAXN << 1], Pre[MAXN << 1];
int H[MAXN << 1], cntE;
void addEdge(int u, int v, ll c, ll f) {
E[++cntE] = (Edge) {v, H[u], c, f};
H[u] = cntE;
E[++cntE] = (Edge) {u, H[v], -c, 0};
H[v] = cntE;
}
void init() {
cntE = -1;
memset(H, -1, sizeof H);
int N = io::gt<int>();
S = 0, T = N << 1 | 1;
rep(i, 1, N + 1) {
ll f = io::gt<ll>();
addEdge(0, i, 0, f);
addEdge(i + N, T, 0, f);
}
rep(i, 1, N) {
addEdge(i, i + 1, 0, INF);
}
int p = io::gt<int>();
int m = io::gt<int>(), f = io::gt<int>();
int n = io::gt<int>(), s = io::gt<int>();
rep(i, 1, N + 1) {
addEdge(0, i + N, p, INF);
}
rep(i, 1, N - m + 1) {
addEdge(i, i + m + N, f, INF);
}
rep(i, 1, N - n + 1) {
addEdge(i, i + n + N, s, INF);
}
}
bool Vis[MAXN];
ll Dis[MAXN];
bool dijkstra() {
std::queue<int> pq;
memset(Dis, 0x3f, sizeof Dis);
memset(Vis, 0, sizeof Vis);
Dis[S] = 0;
pq.push(S);
while (!pq.empty()) {
int x = pq.front(); pq.pop();
Vis[x] = false;
for (int i = H[x]; ~i; i = E[i].nxt) {
int &v = E[i].v;
if (E[i].f > 0 && Dis[v] > Dis[x] + E[i].c) {
Dis[v] = Dis[x] + E[i].c;
Path[v] = i, Pre[v] = x;
if (!Vis[v]) {
pq.push(v);
Vis[v] = true;
}
}
}
}
return Dis[T] != Dis[T + 1];
}
ll mcmf() {
ll cost = 0;
init();
while (dijkstra()) {
ll f = LLONG_MAX;
for (int i = T; i != S; i = Pre[i]) {
f = std::min(f, E[Path[i]].f);
}
cost += f * Dis[T];
for (int i = T; i != S; i = Pre[i]) {
E[Path[i]].f -= f;
E[Path[i]^1].f += f;
}
}
return cost;
}
}
int main() {
printf("%lld\n", mcmf::mcmf());
return 0;
}
体会与心得
- 选择
spfa
还是Dijkstra
真是玄学,有时前者快得多,有时反之 - 问题主要在于转换模型,对本题而言核心在于拆点,从而将状态分成两部分
- 贪心那一步比较显然但是很重要,它使得求解的模型从有上下界的网络流变成了最大流