NOI2017 蔬菜

传送门

NOI 的题果然是非常的难且有意思。还有就是推荐一下command_block 的题解

这题的题意比较难。

题意:有 \(n\) 种菜,初始每种菜有 \(c_i\) 个,单价 \(a_i\),如果不出售每天会变质 \(x_i\) 棵。第一次卖这种菜会获得 \(s_i\) 的奖励。每天至多卖 \(m\) 菜。
给出 \(q\) 次询问,每次询问 \(1\sim q_i\) 天后的最大获利。

这里对变质的理解很重要。

变质的错误理解:无论如何,每天都必然有 \(x_i\) 个变质。
变质的正确理解:每天可以卖掉一些原本今天会变质的蔬菜,这样今天变质的蔬菜数量就 \(<x_i\) 了。

理解完题意,接下来看题解。

算法:模拟费用流。

【费用流模型】

蔬菜减少不太好,考虑时光倒流,从后往前做。每天会加入一些原来会变质的菜,菜可以无限保存。

把每个菜 \(i\) 每天 \(t\) 的状态抽象为 \((t,i)\)

\(S\rightarrow (t,i)\),费用 \(a_i\) 容量为第 \(t\) 天新增的菜 \(i\) 数量。这个数具体是多少先放着。

\((t,i)\rightarrow (t+1,i)\),费用 \(0\) 容量 \(+\infty\),表示菜可以无限保存。

\((t,i)\rightarrow T_t\),费用 \(0\) 容量 \(+\infty\)

\(T_t\rightarrow T\),费用 \(0\) 容量 \(m\),即 \(T_t\) 是负责收取第 \(t\) 天所有售出的菜的结点。

↑ 大概这样。

【一次模拟找性质】

因为图很复杂,考虑增量式分析。

借个 command_block 的图。

不断增加 \(T_i\) 点。

图 II 是新增的点和边,导致了图 III 的三种增广路。接下来是找环。

图 V 的环上每一条边费用都是 \(0\),没有用。

图 IV 的环的费用相当于一条边的费用减去另一条边,不如直接增广路,没有用。

然后怎么做?

上面这个图只能求一个后缀,不能求前缀。

【关注性质】

注意到上面图的一个性质:与 \(S\) 相连的边永不会退流。

这可以推出一个性质:\(p\) 天的最优决策包含第 \(p-1\) 天的最优决策。

具体一点,第 \(p-1\) 天最优决策卖的菜是第 \(p\) 天的子集。

进一步可以得到:假如知道了第 \(p\) 天卖了什么菜,第 \(p'(p'<p)\) 天的最优决策就是取这些菜中前 \(p'\times m\) 大的。(记得 \(m\) 是每天卖菜的最大数量)

现在的问题就是:怎么求出第 \(+\infty\) 天的最优决策。(其实不用 \(+\infty\),求出所有询问天数的最大值的最优决策即可)

【二次模拟求方案】

现在的问题是求出前 \(p\) 天的最优方案。因为现在只要求一个天数的答案,没有时间顺序的限制,所以可以随心所欲处理图。

变成添加一列点。

图 II,图 III 是两条增广路,具体意义为:把今天的菜今天卖/把过去的菜今天卖。

图 IV 图 V 的两个环类似上面的分析,都没有用。

所以我们维护图 II,III 的增广路就行了。

【Code】

点击查看代码
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e5 + 5;

int n, m, Q; //蔬菜种类,每天可卖个数,询问数
int a[N], s[N], c[N], d[N]; //单价,首次奖励,初始个数,变质个数
int qt[N]; //询问

int mxt = -1; //最晚天数
vector<int> add[N]; //add[i]保存所有在第i天第一次出现的菜

int sel[N] = {}; //set[i]表示此时菜i卖了多少个

typedef pair<int, int> pii;
#define mk_pr make_pair
priority_queue<pii> q;

ll ans[N * 10], cur = 0;

int main() {
    cin >> n >> m >> Q;
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> s[i] >> c[i] >> d[i];
    }
    for (int i = 1; i <= Q; i++)
        cin >> qt[i], mxt = max(mxt, qt[i]);
    for (int i = 1; i <= n; i++) {
        if (d[i] == 0)
            add[mxt].push_back(i);
        else
            add[min((c[i] + d[i] - 1) / d[i], mxt)].push_back(i);
    }

    vector<int> v; //v保存当天会增加的菜
    for (int nw = mxt; nw >= 1; nw--) { //从后往前
        //第i种菜应有c[i]-d[i]*(nw-1)-sel[i]个
        for (int i = 0; i < add[nw].size(); i++)
            v.push_back(add[nw][i]);
        for (int i = 0; i < v.size(); i++) {
            if (sel[v[i]] == 0)
                q.push(mk_pr(a[v[i]] + s[v[i]], v[i]));
            else
                q.push(mk_pr(a[v[i]], v[i]));
        }
        v.clear();
        int cnt = m;
        while (cnt) { //只要还有得卖
            if (q.empty()) //没菜了
                break;
            pii tmp = q.top();
//            q.pop();
            int id = tmp.second;
            if (sel[id] == 0) { //还没卖过
                sel[id] = 1;
                cnt--;
                q.pop();
                if (c[id] - sel[id] - (nw - 1) * d[id] > 0) //还有
                    q.push(mk_pr(a[id], id));
                else if (d[id]) //这一次没了,但下一天会加回来
                    v.push_back(id);
                else //永远没了
                    ;
            }
            else {
                int nw_sel = min(cnt, c[id] - sel[id] - (nw - 1) * d[id]); //能卖就卖
                cnt -= nw_sel;
                sel[id] += nw_sel;
                if (c[id] - sel[id] - (nw - 1) * d[id] > 0)
                    q.push(mk_pr(a[id], id)); //没卖完呢
                else if (c[id] - sel[id] - (nw - 1) * d[id] == 0) {
                    q.pop();
                    if (d[id])
                        v.push_back(id); //还有新增
                    else
                        ; //永远没了
                }
            }
        }
    }
    //此时得到了sel[i]:每种菜卖了多少个
    //求ans序列:有哪些收益的菜卖了
    for (int i = 1; i <= n; i++) {
        if (sel[i]) {
            ans[++cur] = a[i] + s[i];
            for (int j = 2; j <= sel[i]; j++)
                ans[++cur] = a[i];
        }
    }
    sort(ans + 1, ans + cur + 1);
    reverse(ans + 1, ans + cur + 1);
    for (int i = 2; i <= cur; i++)
        ans[i] += ans[i - 1];
    for (int i = 1; i <= Q; i++)
        cout << ans[min(qt[i] * m * 1ll, cur)] << endl;
    return 0;
}

posted @ 2024-03-30 10:17  FLY_lai  阅读(19)  评论(0编辑  收藏  举报