修剪草坪

Problem2 修剪草坪(mowlawn.cpp/c/pas)

【题目描述】

在一年前赢得了小镇的最佳草坪比赛后,FJ变得很懒,再也没有修剪过草坪。现在,新一轮的最佳草坪比赛又开始了,FJ希望能够再次夺冠。
然而,FJ的草坪非常脏乱,因此,FJ只能够让他的奶牛来完成这项工作。FJ有N (1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。靠近的奶牛们很熟悉,因此,如果FJ安排超过K(1<=K<=N)只连续的奶牛,那么,这些奶牛就会罢工去开派对:)。因此,现在FJ需要你的帮助,计算FJ可以得到的最大效率,并且该方案中没有连续的超过K只奶牛。

【输入格式】
* 第一行:空格隔开的两个整数N和K
* 第二到N+1行:第i+1行有一个整数E_i

【输出格式】
* 第一行:一个值,表示FJ可以得到的最大的效率值。

【样例输入】

5 2

1

2

3

4

5

 

输入解释:

FJ有5只奶牛,他们的效率为1,2,3,4,5。他们希望选取效率总和最大的奶牛,但是

他不能选取超过2只连续的奶牛

【样例输出】

12

FJ可以选择出了第三只以外的其他奶牛,总的效率为1+2+4+5=12。

【题解】

设计状态:

  f[i][j]表示在考虑前i只奶牛的情况下,从第i只奶牛开始选择了连续j头奶牛的最大效率

状态转移方程:

  当j=0时,f[i][j]=max(f[i-1][0]...f[i-1][K])

  当j>=1且j<=k时f[i][j]=f[i-1][j-1]+e[i]

时间复杂度O(NK) 爆炸

优化:

  当j>=1时转移是O(1)的,这好像没什么好优化的

  当j==0时,你要用到f[i-1][从0到K]的所有状态的最大值

  我们立马就想到可以每次做一层就同时维护这一层的最大值,下一次转移直接O(1)

  但是每算一层新的数,还是要O(K)重新计算最大值,怎么优化呢?

  我们只需要最后一层的最大值作为答案

  我们是否需要计算所有的中间状态?

  f[1][0] f[1][2] f[1][3] f[1][4]

  f[2][0] f[2][2] f[2][3] f[2][4]

  f[3][0] f[3][2] f[3][3] f[3][4]

  每一层的第一个元素也就是f[i][0],都是前面一层的最大值,其他元素是其左上方的元素加上e[i]得到的,那么这几个数的相对大小不改变。如果上一层的最大值在最后一个数取到,那么这一层的最大值就是max(f[i][0],上一层的次大值+e[i]);如果上一层的最大值不在最后一个数取到,那么这一层的最大值是上一层的最大值+e[i]

  仿佛算法的雏形已经显现了,但仔细思考有很大的问题,如果有了最大值、次大值,那么转移就是O(1)的。考虑这样一种情况:上一层的最大值在最后一个数取到,那么你就要用上一层的次大值转移,这很容易,但是这样一来你还要计算这一层的次大值,而次大值的计算没有捷径还是只能O(K)扫描(当然你可以用数据结构优化到O(logK)但这不是今天讨论的重点)。这该怎么搞呢?
  我们一直在讨论最大值、次大值,以及删除最大值之后该怎样找到新的次大值的问题,这很容易让我们想到单调数据结构,这道题用到单调队列。

  维护一个严格单调下降的单调队列(权值,第二下标),假如第i-1层已经计算完了。那么f[i][0]直接取队首就好了,设一个d表示增量,直接给d加上e[i]就表示整个队列里的元素加上了e[i],用队列里的元素的时候就这样q[l]+d,入队的时候要入(x-d);下标的处理同理,就用dk表示增量,每次+1。为了准备下一次转移,我们要把f[i][0]放进去,从队尾开始进入,把所有小于等于它的元素全部剔除,然后将它放在队尾。再从队首开始把所有下标大于K的元素踢掉。循环往复。。。

  其实说的不是很清楚。。。(无所谓咯)

【代码】

#include <cstdio>
#include <algorithm>
#define ll long long
#define maxn 100050
using namespace std;
struct queue
{ll w, pos;}q[maxn];
ll l, r, N, K;
void work()
{
	ll i, j, e, dw=0, dk=0;
	l=1,r=1;
	scanf("%I64d%I64d",&N,&K);
	scanf("%I64d",&e);
	q[r++]=(queue){e,1};
	q[r++]=(queue){0,0};
	for(i=2;i<=N;i++)
	{
		scanf("%I64d",&e);
		dw+=e;
		dk++;
		for(;q[r-1].w+dw<=q[l].w+dw-e;r--);
		q[r++]=(queue){q[l].w-e,-dk};
		for(;q[l].pos+dk>K;l++);
	}
	printf("%I64d\n",q[l].w+dw);
}
int main()
{
	freopen("mowlawn.in","r",stdin);
	freopen("mowlawn.out","w",stdout);
	work();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值