題目描述
cyrcyr今天在種樹,他在一條直線上挖了n個坑。這n個坑都可以種樹,但爲了保證每一棵樹都有充足的養料,cyrcyr不會在相鄰的兩個坑中種樹。而且由於cyrcyr的樹種不夠,他至多會種k棵樹。假設cyrcyr有某種神能力,能預知自己在某個坑種樹的獲利會是多少(可能爲負),請你幫助他計算出他的最大獲利。
輸入輸出格式
輸入格式:
第一行,兩個正整數n,k。
第二行,n個正整數,第i個數表示在直線上從左往右數第i個坑種樹的獲利。
輸出格式:
輸出1個數,表示cyrcyr種樹的最大獲利。
輸入樣例#1:
6 3
100 1 -1 100 1 -1
輸出樣例#1:
200
說明
對於20%的數據,n<=20。
對於50%的數據,n<=6000。
對於100%的數據,n<=500000,k<=n/2,在一個地方種樹獲利的絕對值在1000000以內。
首先應該想到的是O(nk)的dp, 可以獲得50分。
正解:貪心+堆(大根堆)
我們可以把選擇分爲兩種, 選擇 a[i] 和選擇 a[i-1] 、a[i+1]
這樣,將坑每3個分成一組,對於每個a[i], 要麼就選擇a[i],要麼就a[i-1],a[i+1]一起選。
然後堆優化貪心。具體貪心看代碼。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 500010;
struct node
{
ll v;//價值
int id;//編號
bool operator <(node b) const
{
return v < b.v;
}
};
ll a[N], ans;
int l[N], r[N];//鏈表
bool bj[N];//是否可以選擇
priority_queue<node> q;
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
q.push((node){a[i], i});//默認第一種選擇
l[i] = i-1, r[i] = i+1;
}
int len = n;
for(int i = 1; i <= k; i++)
{
while(!q.empty() && bj[q.top().id])
q.pop();
if(q.empty() || q.top().v < 0)
break;//如果堆頂的價值小於0或沒有選擇的,就沒必要繼續選擇,直接退出
node t = q.top();q.pop();
ans += t.v;
bj[t.id] = bj[l[t.id]] = bj[r[t.id]] = 1;//標記不能選
a[++len] = a[l[t.id]]+a[r[t.id]]-a[t.id];
/*新添加一個點, -a[t.id]是做反悔打算,
即不選擇a[t.id]而選擇a[l[t.id]]和a[r[t.id]]*/
int l1 = l[t.id], r1 = r[t.id];
l[len] = l[l1], r[len] = r[r1];
r[l[len]] = len, l[r[len]] = len;//把三個點合成一個點
q.push((node){a[len], len});
}
printf("%lld\n", ans);
return 0;
}