题目
cyrcyr今天在种树,他在一条直线上挖了 n 个坑。
这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。
而且由于cyrcyr的树种不够,他至多会种 k 棵树。
假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
思路
开始没有想到,这是一道可以反悔的贪心题。
我们可以用大根堆来维护最大获利的树,用链表来维护每棵树左右的树。
每次选择了最大的树,我们要把此树左右两边 vis
标记为1
,下次枚举到vis[x] = 1
时 continue
。
但有可能选左右两颗树比选中间这颗树情况更优,于是我们要在加入一颗树,值为 左树权值+右树权值-中间树权值。
这样,在下一次枚举时,当我们选了这颗树,则答案增加了 左树权值+右树权值-中间树权值+中间树权值 = 左树权值+右树权值。 正好选择了2颗树。
注意:不一定要种满 k 颗树
代码
#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#define ll long long
#define mes(x) memset(x,0,sizeof(x))
#define re(x) read(x)
#define il inline
using namespace std;
template <typename T>
il void read(T &x){
x = 0; T sgn = 1; char ch = getchar();
for(;!isdigit(ch);ch = getchar()) if(ch=='-') sgn = -1;
for(;isdigit(ch);ch = getchar()) x = (x<<1)+(x<<3)+(ch^48);
x *= sgn;
}
template <typename T>
il void write(T x){
if(x < 0) x = -x,putchar('-');
if(x == 0) putchar(48);
int cnt = 0, a[70];
while(x > 0){
a[++cnt] = x%10;
x /= 10;
}
for(int i = cnt;i > 0;i--){
putchar(a[i]+48);
}
putchar('
');
}
const int MAXN = 5e5+10;
int n,k,a[MAXN];
bool vis[MAXN];
ll ans = 0,tot = 0;
struct tree{
int pos;
ll val;
bool operator < (const tree &b) const {
return val < b.val;
}
}tr[MAXN];
struct place{
int l,r;
ll val;
}p[MAXN];
priority_queue <tree> q;
il void Del(int x){
p[x].l = p[p[x].l].l;
p[x].r = p[p[x].r].r;
p[p[x].l].r = x;
p[p[x].r].l = x;
}
int main (){
re(n);re(k);
for(int i = 1;i <= n;i++){
re(a[i]);
p[i].l = i-1;
p[i].r = i+1;
p[i].val = a[i];
q.push((tree){i,a[i]});
}
for(int i = 0;i < k;i++){
int x = q.top().pos; q.pop();
if(vis[x]) {
i--;
continue;
}
vis[p[x].l] = vis[p[x].r] =1;
tot += p[x].val;
ans = max(tot,ans);
p[x].val = p[p[x].l].val + p[p[x].r].val - p[x].val;
q.push((tree){x,p[x].val});
Del(x);
}
write(ans);
return 0;
}