这是一道不错的dp转反悔贪心题。
当\(n\)和\(k\)很小的时候,肯定能想到dp。章口就莱。
但是现在\(n \leq 500000\),当场去世。
我们考虑在\(i\)种树,会获得\(a_i\)的获利。
当然,我们也能不在\(i\)种,转而在\(i-1\)和\(i+1\)种。会获得\(a_{i-1}+a_{i+1}\)的利润。
可不可以反悔呢?实际上是可以的。
在选完\(i\)后,我们可以把第\(i\)棵数的价值改下,改为\(a_{i-1}+a_{i+1}-a_i\)。选了这件,相当于不种\(i\),转而种它左右两边的树。对答案是完全没有影响的。
所以我们这么做:
维护一个大根堆,节点储存值和对应下标,以值为关键字。
最开始把所有元素都扔进去。之后进行\(k\)次操作。
每次操作,我们拿出里面的堆顶,然后把答案加上。再对应修改,修改为左右的价值减掉自己的价值。
如何记录左右的元素?直接用一个双向链表就完事啦!
代码:
#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
#define ll long long
const int maxn = 500005;
const int INF = 0x3f3f3f3f;
ll a[maxn];
int n, m;
int l[maxn], r[maxn];
ll ans;
bool done[maxn];
struct Nodes {
ll val, idx;
bool operator < (const Nodes &rhs) const {
return val < rhs.val;
}
};
std::priority_queue<Nodes> heap;
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> a[i];
l[i] = i - 1; r[i] = i + 1;
heap.push((Nodes){a[i], i});
}
r[0] = 1; l[n + 1] = n;
while(m--) {
while(done[heap.top().idx]) heap.pop();
Nodes sb = heap.top(); heap.pop();
if(sb.val < 0) break;
ans += sb.val;
int x = sb.idx;
a[x] = a[l[x]] + a[r[x]] - a[x];
sb.val = a[x];
done[l[x]] = done[r[x]] = true;
l[x] = l[l[x]]; r[l[x]] = x;
r[x] = r[r[x]]; l[r[x]] = x;
heap.push(sb);
}
cout << ans << endl;
return 0;
}