【DP】【单调队列优化】修剪草坪

题目描述

在一年前赢得了小镇的最佳草坪比赛后,约翰变得懒惰了,再也没有修剪过草坪。现在,新一轮的比赛又开始了,约翰希望能够再次夺冠。然而,约翰家的草坪非常脏乱,因此,约翰需要让他的奶牛来完成这项工作。约翰家有N头奶牛,排成一直线,编号为1到N。每只奶牛的能力是不同的,第i头奶牛的能力为Ei。靠在一起的奶牛很熟悉,所以如果安排相邻的K+1头奶牛一起工作,她们就会密谋罢工,所以不能选中连续的K+1头奶牛。因此,约翰需要你的帮助。如何挑选奶牛,才能使她们的能力之和最高呢?

输入

第一行:两个用空格隔开的整数: N N N K K K 1 ≤ N ≤ 100000 , 1 ≤ K ≤ N 1≤ N≤ 100000,1≤ K≤ N 1N1000001KN
第二行到 N + 1 N+1 N+1行:第 i + 1 i+1 i+1行有一个整数,表示第i头牛的能力 E i , 1 ≤ E i < = 1 0 9 Ei,1≤ Ei <= 10^9 Ei1Ei<=109

输出

第一行:单个整数,表示最大的能力之和

样例输入

5 2
1
2
3
4
5

样例输出

12

数据范围限制

对于30% 的数据,有 1 ≤ n ≤ 10 1 ≤ n ≤ 10 1n10
对于60% 的数据,有 1 ≤ n ≤ 2 , 000 1 ≤ n ≤ 2, 000 1n2,000
对于100% 的数据,有 1 ≤ n ≤ 100 , 000 1 ≤ n≤ 100, 000 1n100,000

提示

除了第三头以外的所有奶牛都选,总能力为 1 + 2 + 4 + 5 = 12 1+2+4+5=12 1+2+4+5=12


思路

很(不)显然,这是一道 D P DP DP(!!!)
f i f_i fi 是到第i头牛的最优解
那么,从第 i − k i-k ik头牛到第i头牛肯定有一头牛 j j j不能选 (好惨一牛的),j就是这 k + 1 k+1 k+1头牛的断点

  • f [ i ] = m a x ( f [ i − 1 ] , f [ j − 1 ] + a [ j + 1 ] + a [ j + 2 ] + . . . + a [ i ] ) f[i]=max(f[i-1],f[j-1]+a[j+1]+a[j+2]+...+a[i]) f[i]=max(f[i1],f[j1]+a[j+1]+a[j+2]+...+a[i])

如果用前缀优化一下,那么就是

  • f [ i ] = m a x ( f [ i − 1 ] , f [ j − 1 ] + a [ i ] − a [ j ] ) ; f[i]=max(f[i-1],f[j-1]+a[i]-a[j]); f[i]=max(f[i1],f[j1]+a[i]a[j]);

我们小小的变形一下

  • f [ i ] = m a x ( f [ i − 1 ] , f [ j − 1 ] − a [ j ] ) + a [ i ] ; f[i]=max(f[i-1],f[j-1]-a[j])+a[i]; f[i]=max(f[i1],f[j1]a[j])+a[i];

发现max里面的值只与j有关,所以可以用单调队列优化转移。


雷锋帮助

(作为一个蒟蒻,)我相信我以后肯定看不懂单调队列优化,然后还要回来搜自己写的单调队列优化,so,我来给未来的自己举个栗子。。。
单调队列就是单调递增或者单调递减
有四头牛,他们分别是老大、老二、老三、老四。选牛时,前三个选了,到老四了。嗯~ TA比老三小(选的晚),但是TA比老三巨,所以,老三可以退役了。诶~ 发现老四虐不过老二,那就停止虐菜。好的ヽ( ̄▽ ̄)و,再看老大,发现TA虽然比的其他几个巨,但是TA光荣退役了(不在i-k到i的范围内)。返回一个最巨的,当然是老二啦 (膜拜)


#include<iostream>
#include<cstdio>
using namespace std;
long long a[100100],f[100100],d[100100],n,k;//d是单调队列,f是DP,a是前缀和;
int q[100100],h=1,t=1;//q是队列,h=head,t=tail
long long demo(int i){//让返回值尽量的大,队列单调减,使首元素恒最大 
	d[i]=f[i-1]-a[i];//设个初值
	while(h<=t&&d[q[t]]<d[i])--t;//开始虐菜
	q[++t]=i;//加入dalao光荣榜
	while(h<=t&&q[h]<i-k)h++; //大佬退役
	return d[q[h]];//返回最大值
}
int main(){
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		a[i]+=a[i-1];//前缀处理
	}
	for(int i=1;i<=n;i++)
		f[i]=demo(i)+a[i];
	printf("%lld",f[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值