修剪草坪/选择数字(单调队列优化DP)
题意:给定n个非负整数\(a[1]...a[n]\).现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大.
分析:正难则反,设\(f[i]\)表示表示不选第i头牛的最小代价,结果会发现:\(f[i]=min(f[j])+a[i]\),于是可以单调队列,\(O(n)\)优美地过了本题.
ll n,k,sum,cnt=1e16;
ll val[100005];
ll q1[100005],q2[100005],f[100005];
int main(){
n=read();k=read();
for(ll i=1;i<=n;i++){
val[i]=read();
sum+=val[i];//记录下所有数的和
}
int head=0,tail=0;//设置队列头,尾指针
for(int i=1;i<=n;i++){
f[i]=q1[head]+val[i];
//当前的第i个数不选,表示出最小代价f[i]
//q1[]是从小到大的单调队列,记录的是不选的代价
while(head<=tail&&q1[tail]>f[i])
tail--;
//要把当前不选的代价塞进单调队列q1中
while(head<=tail&&q2[head]<i-k)
head++;
//q2[]也是从小到大的单调队列,记录的是数的编号(下标)
//因为头尾指针都是head,tail,所以两个队列是并行的.
//换句话说,两个队列中的值一一对应.
q1[++tail]=f[i];
q2[tail]=i;
//把当前代价和不选的数的编号分别入队.
}
for(int i=n-k;i<=n;i++)
cnt=min(cnt,f[i]);
//要拿总和减去最小代价,当然是找最小值啦!
printf("%lld\n",sum-cnt);
return 0;
}