反悔贪心

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;
}
posted @ 2022-11-02 08:57  MistZero  阅读(26)  评论(0编辑  收藏  举报