反悔贪心
P2949 Work Scheduling G:有 \(n\) 个事件,每个事件有一个截止日期和收益,每个时间点可以完成一个事件,求最大收益和。
可以开一个堆来维护当前选定的所有事件,按照截止日期排序。
显然所有截止日期靠后的事件也可以在前面完成,所以满足反悔。
每一次如果选定事件数小于截止日期就可以直接放进堆里面,更新答案。
否则,如果堆顶元素(选定的所有事件中的最小收益)小于当前事件收益就可以弹掉堆顶,放进当前事件,更新答案。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, res; struct Node {
int d, val;
inline bool operator <(const Node &X) const {
return val > X.val;
}
} b[N]; priority_queue <Node> q;
inline bool cmp(Node x, Node y) { return x.d < y.d; }
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n; for (int i = 1; i <= n; ++i) cin >> b[i].d >> b[i].val;
sort(b + 1, b + 1 + n, cmp);
for (int i = 1; i <= n; ++i) {
if (q.size() < b[i].d) q.push(b[i]), res += b[i].val;
else if (q.top().val < b[i].val)
res -= q.top().val - b[i].val, q.pop(), q.push(b[i]);
}
cout << res << endl;
return 0;
}
CF865D Buy Low Sell High:每天股票有一个价格,每天可以买入 / 卖出一支股票,求最大收益和。
同样地,建立一个小根堆,每次把买入的股票加进去然后反悔即可。
如何知道加了哪些股票呢?很简单,只要把每一支股票都视为加入,如果要反悔就统计答案。
最后留在堆里面的就不用管了。
有一个小 trick。由于最后产生的收益为卖出 \(-\) 买入,所以可以拆贡献。
也就是说,我们并不关心中间的处理过程,我们只关心最后这一对买入 - 卖出产生的收益。
假设买入花费 \(x\) 代价,第一次发现可以卖出获得收益 \(y>x\),那么净收益为 \(y-x\)。此时将 \(x\) 弹出,\(y\) 加入序列。
假设现在又有一个卖出的机会 \(z>y\),找到堆顶 \(y\),那么净收益为 \(z-y\)。
我们发现,此时产生的收益等价于将 \(x\) 和 \(z\) 进行配对,净收益为 \((z-y)+(y-x)=z-x\)。
也就是说,可以忽略这个 \(y\) 的存在,当作中间变量即可。
除此之外,由于最终收益为 \(\sum\) 卖出 \(- \sum\) 买入,所以我们也不关心它们是怎样配对的。
只需要知道每一次反悔的 \(\Delta ans\) 即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 10; int n, res;
priority_queue <int, vector <int>, greater <int> > q;
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n; for (int i = 1, x; i <= n; ++i) {
cin >> x; q.push(x);
if (q.top() < x) res += x - q.top(), q.pop(), q.push(x);
}
cout << res << endl;
return 0;
}
CF13C Sequence:对于一个数列,每次可以把某一位 \(+1\) 或 \(-1\),求使得整个序列单调非降的最小操作数。
贪心地想,使得操作次数最少可以转化为改变的元素最少,即改动后的数组内一定可以全为原数组内的元素。
令原数组为 \(a\),重排后的数组为 \(b\)。
考虑设 \(dp_{i,j}\) 表示当前处理到 \(a_i\),以 \(b_j\) 结尾的最小操作数。
那么有 \(dp_{i,j}=\min\{dp_{i,j-1},dp_{i-1,j}+|a_i-b_j|\}\)。
对于每一个 \(i\),可以发现,到某个 \(j\) 开始往后的值都变得一样。
也就是说有大量无用状态。
那么可以考虑贪心(见 P4597 Sequence 加强版)。
受到前面 \(dp\) 答案的启发,贪心地想可以发现,被更改成为 \(b_j\) 的元素越小,它就越容易成为一个单调非降的序列。
考虑反悔,即新建一个大根堆,每一次提出选择过的最大元素。
如果当前元素比大根堆顶更小,即不满足非降,就踢掉原来的堆顶,放上当前元素成为堆顶,更新答案。
答案更新加上两者差即可,同理于之前求 \(a\) 和 \(b\) 求差的过程。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3 + 10;
int n, res;
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n; priority_queue <int> q;
for (int i = 1, x; i <= n; ++i) {
cin >> x; q.push(x);
if (x < q.top()) res += q.top() - x, q.pop(), q.push(x);
}
cout << res << endl;
return 0;
}