Processing math: 100%
跳转至

Company

题意

在第 i 天会有 mi 名员工上班,公司每天都要为每位上班的员工发一条消毒毛巾。一条消毒毛巾在使用后,可以选择花费 afa 元消毒,也可以花费 bfb 元消毒。购买新的消毒毛巾要花费 f 元。求公司最少要花费多少钱。

解析

这道题是费用流经典问题 餐巾计划 的加强版,n 扩大到了 105。那么我们就不能使用费用流了。

假设我们已经确定了购买毛巾的数量 x。维护三个双端队列 qb,qa,qf,每次使用完毛巾后就将其推入 qf,表示距使用完不足 a 天的所有毛巾;若 qf 的队首距使用完达到 a 天,则推入 qa;若 qa 的队首距使用完达到 b 天,则推入 qb。若当前可用毛巾数量不足 mi,则优先从 qb 中获取没洗的毛巾,每件代价为 fb;否则从 qa 的尾部获取没洗的毛巾,每件代价为 fa

于是对于确定的 x,我们能够在 O(n) 的时间复杂度内计算答案了。感性理解,答案关于 x 是凸的,可以三分取最优值。

实现

#include <bits/stdc++.h>

const int maxn = 1e5 + 19;

int n, a, b, f, fa, fb, m[maxn];

int check(int x){
    int res = x * f, now = x;
    std::deque<std::pair<int, int> > qb, qa, qf;
    for(int i = 1; i <= n; ++i){
        while(!qf.empty() && qf.front().first + a + 1 <= i)
            qa.push_back(qf.front()), qf.pop_front();
        while(!qa.empty() && qa.front().first + b + 1 <= i)
            qb.push_back(qa.front()), qa.pop_front();
        while(now < m[i]){
            if(!qb.empty()){
                int d = std::min(qb.front().second, m[i] - now);
                qb.front().second -= d, now += d, res += d * fb;
                if(!qb.front().second) qb.pop_front();
            }
            else if(!qa.empty()){
                int d = std::min(qa.back().second, m[i] - now);
                qa.back().second -= d, now += d, res += d * fa;
                if(!qa.back().second) qa.pop_back();
            }
            else return 1e9;
        }
        now -= m[i], qf.push_back(std::make_pair(i, m[i]));
    }
    return res;
}

int main(){
    std::scanf("%d%d%d%d%d%d", &n, &a, &b, &f, &fa, &fb);
    int l = 0, r = 0;
    for(int i = 1; i <= n; ++i)
        std::scanf("%d", m + i), l = std::max(l, m[i]), r += m[i];
    while(r - l > 6){
        int m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
        if(check(m1) < check(m2)) r = m2;
        else l = m1;
    }
    int ans = 1e9;
    for(int i = l; i <= r; ++i) ans = std::min(ans, check(i));
    std::printf("%d\n", ans);
    return 0;
}

来源

NOI.AC #2245 company

评论

Gitalking ...