【DP】【cofun1041】修建草坪

【cofun1041】修建草坪

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

Input Format
第一行:两个用空格隔开的整数:N和K,1 ≤ N ≤ 105 ,1 ≤ K ≤ N
第二行到N + 1行:第i + 1行有一个整数,表示第i头牛的能力Ei,1 ≤ Ei ≤ 109
Output Format
第一行:单个整数,表示最大的工作能力之和

Sample Input
5 2
1
2
3
4
5
Sample Output
12

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

Source
USACO 2011 open


分析:
满足无后效性,DP。
1. 求出能力的前缀和。
2. n <= 8000时,直接DP,转移方程:

for(i = 1; i <= k; i ++)
    f[i] = s[i];
for(i = k + 1; i <= n; i ++)
{
    f[i] = f[i - 1];
    for(j = i - 2; j >= i - k - 1; j --)
    f[i] = max(f[i], f[j] + s[i] - s[j + 1]);
 }

f[i]:到第i只奶牛为止满足要求的最高工作能力。
3. n <= 105 时,考虑降维。可以发现,每次更新f[i]就是要找到连续一段与i一起工作,使得能力最大。参考上面的方程,可以利用优化队列存下f[j]-s[j+1],转移方程:

for(i = 1; i <= n; i ++)
{
    for(; head <= tail && q[head][0] <= i - k - 1; head ++);
    f[i] = max(f[i - 1], q[head][1] + s[i]);
    for(; head <= tail && q[tail][1] < f[i - 1] - s[i]; tail --);
    q[++ tail][0] = i, q[tail][1] = f[i - 1] - s[i];
 }

  • 代码:
#include <bits/stdc++.h>
 using namespace std;

 int n, k, i, j, e, head, tail;
 long long f[100005], s[100005], q[100005][2];

 int main()
 {

    scanf("%d%d", &n, &k);
    for(i = 1; i <= n; i ++) 
        scanf("%d", &e), s[i] = s[i - 1] + e;

    /*for(i = 1; i <= k; i ++)
        f[i] = s[i];
    for(i = k + 1; i <= n; i ++)
    {
        f[i] = f[i - 1];
        for(j = i - 2; j >= i - k - 1; j --)
        f[i] = max(f[i], f[j] + s[i] - s[j + 1]);
    }*/
    for(i = 1; i <= n; i ++)
    {
        for(; head <= tail && q[head][0] <= i - k - 1; head ++);
        f[i] = max(f[i - 1], q[head][1] + s[i]);
        for(; head <= tail && q[tail][1] < f[i - 1] - s[i]; tail --);
        q[++ tail][0] = i, q[tail][1] = f[i - 1] - s[i];
    }

    printf("%lld", f[n]);

    return 0;

 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值