[CCO 2020] Shopping Plans 题解

前言

题目链接:洛谷UOJ

本文给出一种仅用一个堆的实现,本质和用黑盒是一致的。

题意简述

\(n\) 个商品 \((a_i,c_i)\),分别表示该商品的种类和价格。共 \(m\) 种商品,对于第 \(i\) 种商品,你必须购买 \([l_i,r_i]\) 个商品。

你要求出前 \(k\) 便宜的方案所需的价钱,如果没有这样的方案,输出 \(-1\)

认为两种方案不同,当且仅当存在一种商品在两种方案中购买个数不同。

\(n,m,k\leq2\times10^5\)

题目分析

不妨先考虑 \(m=1,l_i=r_i\) 的情况,令 \(x=l_i\)。显然要对这些商品先按照价格从小到大排序。

那么一开始最便宜的方案,就是选择 \([1,x]\) 这些商品,第二便宜的是 \([1,x-1]\cup\{x+1\}\),但是第三便宜的方案就需要分类讨论了。

我们似乎在进行不断拓展方案的过程,并且每个方案的后继状态是不多的,可以用个堆来维护,这样就可以写出一些不太正确的代码。

考虑如何做到不重不漏。不难想到钦定这样一种顺序,用 \((\mathrm{lb},\mathrm{lim},\mathrm{cur})\) 表示一个状态,\([1,\mathrm{lb}]\) 选满了,\(\mathrm{cur}\)\([\mathrm{lb+1},\mathrm{lim}-1]\) 之间,从左向右滑动。每次我们可以让 \(\mathrm{cur}\) 向右移动一位,\((\mathrm{lb},\mathrm{lim},\mathrm{cur}+1)\),也可以让 \(\mathrm{lb}\) 成为新的滑块,让 \(\mathrm{cur}\) 成为 \(\mathrm{lim}\)\((\mathrm{lb}-1,\mathrm{cur},\mathrm{lb}+1)\)。初始显然有 \((x-1,\infty,x)\)

考虑 \(l_i\leq r_i\),也就是我们可以新增一个商品。同样考虑不重不漏,不妨钦定只能在初始状态后面新增,即只有在 \(\mathrm{lb}+1=\mathrm{cur}\) 的时候转移到 \((\mathrm{lb}+1,\infty,\mathrm{cur}+1)\)

多个商品,为了钦定顺序,多维护一个单调的指针 \(p\)。每次可以在 \(p\) 这种商品上做之前的转移,也可以让 \(p\) 继续移动。那 \(p\) 移动顺序要如何呢?不难想到按照一开始 \(v_{l_i+1}-v_{l_i}\) 排序,即商品第二便宜减去第一便宜的价钱,每次可以撤销再让 \(p\) 后移,或者直接后移,分别对应当前种类商品不再选择,和在初始状态基础上再选择一些。

以上过程用一个堆就能维护,每次转移的后继状态是 \(\mathcal{O}(1)\) 的,只需做 \(k\) 次,所以是 \(\mathcal{O}(n\log n+k\log k)\) 的。

代码

#include <cstdio>
#include <iostream>
#include <tuple>
#include <queue>
#include <algorithm>
using namespace std;

template <typename T>
using minHeap = priority_queue<T, vector<T>, greater<T>>;

using ll = long long;

const int N = 2e5 + 10;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

int n, m, k;
vector<int> v[N];
int L[N], R[N];
pair<ll, int> a[N];
int va, inv[N];

minHeap<tuple<ll, int, int, int, int>> Q;

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1, a, c; i <= n; ++i) {
        scanf("%d%d", &a, &c);
        v[a].emplace_back(c);
    }
    ll sum = 0;
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &L[i], &R[i]);
        if (R[i] > (int)v[i].size())
            R[i] = v[i].size();
        if (L[i] > R[i]) {
            while (k--) puts("-1");
            return 0;
        }
        sort(v[i].begin(), v[i].end());
        for (int o = 0; o < L[i]; ++o) {
            sum += v[i][o];
            sum = min(sum, INF);
        }
        
        if (R[i] && L[i] < (int)v[i].size()) {
            if (L[i] == 0) a[++va] = { v[i][0], i };
            else a[++va] = { v[i][L[i]] - v[i][L[i] - 1], i };
        }
    }
    sort(a + 1, a + va + 1);
    for (int i = 1; i <= va; ++i)
        inv[a[i].second] = i;
    Q.emplace(sum, 0, 0, 0, 0);
    while (!Q.empty() && k) {
        auto [sum, p, ql, lim, cur] = Q.top();
        Q.pop();
        if (sum >= INF) {
            break;
        }
        if (!p || lim < (int)v[p].size() + 1 || cur > L[p]) {
            printf("%lld\n", sum), --k;
        }
        if (inv[p] + 1 <= va) {
            int q = a[inv[p] + 1].second;
            if (p && cur == L[p] + 1 && lim == (int)v[p].size() + 1 && (!ql || ql + 1 < cur)) {
                Q.emplace(sum - a[inv[p]].first, q, L[q] - 1, (int)v[q].size() + 1, L[q]);
            }
            if (!(p && cur == L[p] && lim == (int)v[p].size() + 1 && ql == L[p] - 1)) {
                Q.emplace(sum, q, L[q] - 1, (int)v[q].size() + 1, L[q]);
            }
        }
        if (p) {
            if (lim == (int)v[p].size() + 1 && ql + 1 == cur && cur < R[p])
                Q.emplace(sum + v[p][cur], p, ql + 1, lim, cur + 1);
            if (cur >= 1 && cur + 1 < lim)
                Q.emplace(sum - v[p][cur - 1] + v[p][cur], p, ql, lim, cur + 1);
            if (ql >= 1 && ql + 1 < cur)
                Q.emplace(sum - v[p][ql - 1] + v[p][ql], p, ql - 1, cur, ql + 1);
        }
    }
    while (k--) puts("-1");
    return 0;
}
posted @ 2025-07-21 10:42  XuYueming  阅读(22)  评论(0)    收藏  举报