[CCO 2020] Shopping Plans 题解
前言
本文给出一种仅用一个堆的实现,本质和用黑盒是一致的。
题意简述
有 \(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;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18995320。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号