【网络流24题】餐巾计划问题

本文介绍了一种利用最小费用最大流算法解决纸巾使用问题的方法。问题设定为在限定天数内,每天需要使用特定数量的纸巾,通过购买新纸巾、快洗或慢洗旧纸巾来满足需求,目标是最小化总花费。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

nn 天,每天需要 Ri 块干净的纸巾。纸巾用完一次就脏了,可以选择清洗后使用或不再使用。

已知新买纸巾的价格为 pp ,花 m 天快洗纸巾的价格为 ff ,花 n 天慢洗纸巾的价格为 ss

求最小花费。

数据范围

n2000Ri107Ri≤107p,f,s104p,f,s≤104

题解

考虑网络流算法。

第一天为源点 SS ,最后一天为汇点 T ,纸巾为流量,价格为费用。

通过简单贪心可以得到每天的干净纸巾刚好为所需要的数量时,总花费一定最小。

由此问题转化为求建图后跑满流的情况下的最小费用。

最小费用最大流。

考虑如何使得满足每天恰好使用 RiRi 块纸巾。

将每天拆成两个点,即使用前 XiXi 和使用后 YiYi

建图方法如下((c,f)(c,f) 即费用为 cc、流量为 f 的边):

  • SSXi 连接一条(0,Vi)(0,Vi) 的边,即第 ii 天纸巾需要的数量;
  • YiTT 连接一条 (0,Vi) 的边,即第 ii 天使用的纸巾的数量;
  • SYiYi 连接一条 (p,inf)(p,inf) 的边,即第 ii 天购买新纸巾,花费为 p
  • XiXiXi+1Xi+1 连接一条 (0,inf)(0,inf) 的边,即第 ii 天没用完的纸巾留给第 i+1 天,没有费用;
  • XiXiYi+mYi+m 连接一条 (f,inf)(f,inf) 的边,即第 ii 天快洗后的纸巾第 i+m 天拿到干净纸巾,花费为 ff
  • XiYi+nYi+n 连接一条 (s,inf)(s,inf) 的边,即第 ii 天慢洗后的纸巾第 i+n 天拿到干净纸巾,花费为 ss

由于所有与源点 S 的所有边都与每一个 XiXi 相连,由此保证了每天都有 RiRi 的流量流入,即 RiRi 块新纸巾。

由于所有与汇点 TT 的所有边都与 Yi 相连,由此限制了每天必须有 RiRi 的流量流出(因为是跑最大流)。

可以发现所有所有与 TT 相连的边 e ,边权和 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真是玄学,有时前者快得多,有时反之
  • 问题主要在于转换模型,对本题而言核心在于拆点,从而将状态分成两部分
  • 贪心那一步比较显然但是很重要,它使得求解的模型从有上下界的网络流变成了最大流
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值