[Luogu P1251] [网络流24题] 餐巾计划问题

洛谷传送门

题目描述

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

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

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

输入输出格式

输入格式:

由标准输入提供输入数据。文件第 1 1 行有 1 1 个正整数 N N ,代表要安排餐巾使用计划的天数。

接下来的 N N 行是餐厅在相继的 N N 天里,每天需用的餐巾数。

最后一行包含 5 5 个正整数 p,m,f,n,s p , m , f , n , s p p 是每块新餐巾的费用; m m 是快洗部洗一块餐巾需用天数; f f 是快洗部洗一块餐巾需要的费用; n n 是慢洗部洗一块餐巾需用天数; s s 是慢洗部洗一块餐巾需要的费用。

输出格式:

将餐厅在相继的 N N 天里使用餐巾的最小总花费输出

输入输出样例

输入样例#1:

3
1 7 5 
11 2 2 3 1

输出样例#1:

134

说明

N<=2000 N <= 2000

ri<=10000000 r i <= 10000000

p,f,s<=10000 p , f , s <= 10000

解题分析

费用流的经典模型。

考虑如何建边:我们显然不能像题目所述的一样, 将白天的点又连回晚上的点, 这样就会混淆退流边和表示餐巾清洗的边,并且费用加在哪里不好确定。

因此我们将图建成二分图模型,连以下 6 6 类边:

  • 源点 S S 向每天白天连费用为 p p , 流量为 INF I N F 的边, 表示每天白天可以买新的餐巾。
  • 源点 S S 向每天晚上连费用为 0 0 ,流量为当天餐巾使用量的边, 表示每天需要使用的餐巾在晚上变成了旧餐巾,需要清洗。
  • 每天晚上向第二天晚上连费用为 0 0 ,流量为 INF I N F 的边, 表示第一天的餐巾可以留到第二天。(其实还有一个作用:保证源点流入的流量始终和流出的流量平衡。如果我们不这样连的话即意味着第 i i 天的餐巾必须洗完后第 i+m i + m i+n i + n 天使用。当然也可以在每天早上向第二天早上连边, 效果相同。)
  • i i 天晚上向第 i+m i + m 天早上连费用为 f f ,流量为 INF I N F 的边, 表示第 i i 天晚上快洗的餐巾第 i+m i + m 天早上可以使用。
  • i i 天晚上向第 i+n i + n 天早上连费用为 f f ,流量为 INF I N F 的边, 表示第 i i 天晚上慢洗的餐巾第 i+n i + n 天早上可以使用。
  • 每天白天向汇点 T T 连费用为 0 0 ,流量为当天餐巾使用量的边,表示每天白天需要这么多的餐巾。

这样我们一路增广至最大流时即可满足。

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 100050
#define S 50000
#define T 50001
#define INF 999999999
template <class TT>
IN void in(TT &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
struct Edge
{
    int to, flow, cost, nex;
}edge[MX << 1];
int slow, fast, day, cnt = -1, nw, fc, sc, hd, tl;
long long ans;
int head[MX], rec[MX], del[MX], que[MX], dis[MX];
bool inq[MX];
IN void add(const int &from, const int &to, const int &cost, const int &fl)
{
    edge[++cnt] = {to, fl, cost, head[from]}; head[from] = cnt;
    edge[++cnt] = {from, 0, -cost, head[to]}; head[to] = cnt;
}
IN bool SPFA()
{
    R int now;
    que[tl = hd = 1] = S;
    std::memset(dis, 63, sizeof(dis));
    del[S] = INF; dis[S] = 0;
    W (tl <= hd)
    {
        now = que[tl++];
        for (R int i = head[now]; ~i; i = edge[i].nex)
        {
            if(edge[i].flow > 0 && dis[edge[i].to] > dis[now] + edge[i].cost)
            {
                del[edge[i].to] = std::min(edge[i].flow, del[now]);
                dis[edge[i].to] = dis[now] + edge[i].cost;
                rec[edge[i].to] = i;
                if(!inq[edge[i].to]) inq[edge[i].to] = true, que[++hd] = edge[i].to;
            }
        }
        inq[now] = false;
    }
    return dis[T] < 99999999;
}
IN void updata()
{
    ans += del[T] * dis[T];
    R int now = T, tar;
    W (now != S)
    {
        tar = rec[now];
        edge[tar].flow -= del[T];
        edge[tar ^ 1].flow += del[T];
        now = edge[tar ^ 1].to;
    }
}
long long EK()
{
    W (SPFA()) updata();
    return ans;
}
int main(void)
{
    in(day); int a, b;
    std::memset(head, -1, sizeof(head));
    for (R int i = 1; i <= day; ++i)
    in(a), add(i, T, 0, a), add(S, i + day, 0, a);
    in(nw), in(fast), in(fc), in(slow), in(sc);
    for (R int i = 1; i <= day; ++i) add(S, i, nw, INF);
    for (R int i = 1; i <  day; ++i) add(day + i, day + i + 1, 0, INF);
    int bd = std::max(0, day - fast);
    for (R int i = 1; i <=  bd; ++i) add(day + i, i + fast, fc, INF);
    bd = std::max(0, day - slow);
    for (R int i = 1; i <=  bd; ++i) add(day + i, i + slow, sc, INF);
    printf("%lld", EK());
}

你可能感兴趣的:(网络流,费用流)