题解 P3545 【[POI2012]HUR-Warehouse Store】
P3545 [POI2012]HUR-Warehouse Store
Describe
一共 \(n\) 天,每天上午会进 \(A_i\) 的物品,中午会有一个客人想要买走 \(B_i\) 的物品,当然你也可以选择不买,问你最后最多可以交易多少次。
数据范围 : \(1 <= n <= 250000, 0 <= A_i, B_i <= 10^9\)
Solution
首先,我们看到题目先会想到 \(01\) 背包。是个正确的作法,但是肯定过不了。
然后,我们考虑到这一定是个贪心/推式子的题目。
但是推式子的题目一般的特性在这里显然不符合(推式子的题目一般不会让你选择)
那接下来考虑怎么去贪心。
第一想法是我们选最小的肯定是最优的,因为就算选了这个最小的而导致其他的不可以选,那也最多只能导致一个不可以选。
因为如果可以导致多个不可以选的话,选了次小的哪一个也会导致更多的不可以选。
所以选择最小的策略是对的,\(sort\) 一般的复杂度我们也可以接受,但是问题来了,我们那些数可以取?
取完当前最小的,那么它前面的一些数说不定不可以再去。
那么处理这些的复杂度就不稳定了,可以构造数据卡掉。
接下来我们想每天上午进的货物会对什么有影响。
这个很显然,它可以对他后面的所有数产生影响,因为到货了就可以卖给别人了。
那是不是可以这么考虑,从后往前去跑,每次去当前可以取的最小的。
因为从后完全就去除了它的后效性,所有正确性也是对的。
但是新增一个数 \(A_i\) 可能很大,一次可以处理多个数。
那你就必须保证后面整个序列是有序的,而且还要维护一个后缀和。
我们考虑维护这个有序数列的复杂度最优,那就是往前加一个数,就把这个数加入到数列里面去。
这样的复杂度最劣是 \(O(n^2)\) 的,还是会炸。
那我们是不是无法从一些顺序上去找到优化的空间。
我们就可以考虑去莽,然后支持撤销,这样也可以保证正确性。
具体的话是这样实现的:
从前往后读入,每次可以取就取,并且把这些取过的记录在单调队列中。
如果当前这个放不下,我们就把他和队列首的元素做比较,不断更新即可。
根据我们的第二个想法,每个前面取过的如果弹出来,因为他的后效性,肯定是可以服务后面的。
那么就结束了。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 250010;
int n, sum, ans, book[maxn];
priority_queue<pair<int, int>, vector<pair<int, int> > > Q;
struct node {
int a, b, id;
} e[maxn];
int cmp(node x, node y) {
return x.b < y.b;
}
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &e[i].a);
e[i].id = i;
}
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &e[i].b);
}
for(int i = 1; i <= n; ++ i) {
sum += e[i].a;
if(e[i].b < sum) {
Q.push(make_pair(e[i].b, e[i].id));
++ ans;
book[i] = 1;
sum -= e[i].b;
}
else if(! Q.empty() && Q.top().first > e[i].b) {
book[Q.top().second] = 0;
book[i] = 1;
sum += e[Q.top().second].b - e[i].b;
Q.pop(); Q.push(make_pair(e[i].b, e[i].id));
}
}
printf("%lld\n", ans);
for(int i = 1; i <= n; ++ i) {
if(book[i]) {
printf("%d ", i);
}
}
return 0;
}
Ps:本人知道题解前不应该放错误作法,但是觉得这些错误作法也是一些解题的思路,这里用不上,其他地方可能用到上,望批准。