题解 luogu P1251 【餐巾计划问题】

题解 luogu P1251 【餐巾计划问题】

时间:2019.3.19

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

题目描述

一个餐厅在相继的 \(N\) 天里,每天需用的餐巾数不尽相同。假设第 \(i\) 天需要 \(r_i\) 块餐巾(\(i=1,2,...,N\))。餐厅可以购买新的餐巾,每块餐巾的费用为 \(p\) 分;或者把旧餐巾送到快洗部,洗一块需 \(a\) 天,其费用为 \(f\) 分;或者送到慢洗部,洗一块需 \(b\) 天(\(b>a\)),其费用为 \(s\) 分(\(s < f\))。

每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。

试设计一个算法为餐厅合理地安排好 \(N\) 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

建图(1)

考虑建出下面这张图:

将每天当成一个点,根据当天的餐巾是否已使用,拆成
\(S\)连边,费用为\(p\),表示可以任意买新餐巾
连边,容量为\(r_i\),表示需要使用\(r_i\)条餐巾。
\(N\)\(T\)连边,容量为\(\sum r_i\),表示需要将所有餐巾用掉。
每天\(a\)天后的连边,费用为\(s\),表示送到快洗部(慢洗部同理)


这样,我们只需要控制流向的流量均为\(r_i\)就行了。

但是,这样就出现一个问题:我们没办法控制流向后的流量刚好流满呀?

看来我们要改变建图方式了。

建图(2)

建出下面这张图:

将每天当成一个点,根据当天的餐巾是否已使用,拆成
\(S\)连边,费用为\(p\),表示可以任意买新餐巾。
\(T\)连边,容量为\(r_i\)表示要消耗\(\textbf{r}_\textbf{i}\)条餐巾。
\(S\)连边,容量为\(r_i\)表示收到用前产生的\(\textbf{r}_\textbf{i}\)条旧餐巾
每天\(a\)天后的连边,费用为\(s\),表示送到快洗部(慢洗部同理)


这样建图意味着的流与的流意义不同。
\(S\)(或几天前的)流向的流表示能使用的新餐巾,
而从\(S\)流向的流表示使用后留下的\(r_i\)条旧餐巾。

我们控制了从流出的餐巾(即使用数量)为\(r_i\),而流进的餐巾数量刚好为\(r_i\)

代码

注意上面没有讲到的一点:使用前多余的干净餐巾可以留到明天再使用。
将今天的向明天的连边即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int kMaxN = 2000 * 2 + 10;
const int kMaxM = kMaxN * 4;
const int kInf = 4e18;
struct Graph {
  struct Arc {
    int to, cap, cost;
    int next;
  };
  Arc arcs[kMaxM << 1];
  int top, head[kMaxN];
  Graph() { top = 1; }
  void Add(int u, int v, int cap, int cost) {
    arcs[++top] = (Arc) {v, cap, cost, head[u]};
    head[u] = top;
    arcs[++top] = (Arc) {u, 0, -cost, head[v]};
    head[v] = top;
  }
  int s, t;
  queue<int> Q;
  bool in[kMaxN];
  int cost[kMaxN], flow[kMaxN], last[kMaxN];
  bool Spfa() {
    memset(flow, 0, sizeof(flow));
    memset(last, 0, sizeof(last));
    memset(cost, 0x7F, sizeof(cost));
    // 不用memset flow和last
    // 这两个数组不参与最短路计算
    // 一定要写flow[t] = 0
    // 如果有负环一定要写memset(in)
    Q.push(s);
    cost[s] = 0;
    flow[s] = kInf;
    flow[t] = 0; // !!!
    while (!Q.empty()) {
      int u = Q.front();
      Q.pop();
      in[u] = false;
      for (int i = head[u]; i; i = arcs[i].next) {
        Arc& arc = arcs[i];
        int v = arc.to;
        if (cost[u] + arc.cost < cost[v] && arc.cap) {
          cost[v] = cost[u] + arc.cost;
          flow[v] = min(flow[u], arc.cap);
          last[v] = i;
          if (!in[v]) {
            in[v] = true;
            Q.push(v);
          }
        }
      }
    }
    return flow[t] != 0;
  }
  int max_flow, min_cost;
  void Update() {
    int u = t;
    while (u != s) {
      int i = last[u];
      arcs[i].cap -= flow[t];
      arcs[i ^ 1].cap += flow[t];
      u = arcs[i ^ 1].to;
    }
    max_flow += flow[t];
    min_cost += cost[t] * flow[t];
  }
  void Ek() {
    max_flow = min_cost = 0;
    while (Spfa()) Update();
  }
};
Graph G;
int n;
int p, a, f, b, s;
#define UNUSED(I) (I)
#define USED(I) ((I) + 2001)
signed main() {
  scanf("%lld", &n);
  G.s = kMaxN - 2;
  G.t = kMaxN - 1;
  for (int i = 1; i <= n; i++) {
    int r;
    scanf("%lld", &r);
    G.Add(UNUSED(i), G.t, r, 0);
    G.Add(G.s, USED(i), r, 0);
  }
  scanf("%lld %lld %lld %lld %lld",
        &p, &a, &f, &b, &s);
  for (int i = 1; i <= n; i++) {
    G.Add(G.s, UNUSED(i), kInf, p);
    if (i + 1 <= n) G.Add(UNUSED(i), UNUSED(i + 1), kInf, 0);
    if (i + a <= n) G.Add(USED(i), UNUSED(i + a), kInf, f);
    if (i + b <= n) G.Add(USED(i), UNUSED(i + b), kInf, s);
  }
  G.Ek();
  // printf("%lld %lld\n", G.min_cost, G.max_flow);
  printf("%lld\n", G.min_cost);
  return 0;
}
原文地址:https://www.cnblogs.com/longlongzhu123/p/10559539.html