AcWing 1087. 修剪草坪

\(AcWing\) \(1087\). 修剪草坪

一、题目描述

在一年前赢得了小镇的最佳草坪比赛后,\(FJ\) 变得很懒,再也没有修剪过草坪。

现在,新一轮的最佳草坪比赛又开始了,\(FJ\) 希望能够再次夺冠。

然而,\(FJ\) 的草坪非常脏乱,因此,\(FJ\) 只能够让他的奶牛来完成这项工作。

\(FJ\)\(N\) 只排成一排的奶牛,编号为 \(1\)\(N\)

每只奶牛的效率是不同的,奶牛 \(i\) 的效率为 \(E_i\)

编号相邻的奶牛们很熟悉,如果 \(FJ\) 安排 超过 \(K\) 只编号连续 的奶牛,那么这些奶牛就会罢工去开派对。

因此,现在 \(FJ\) 需要你的帮助,找到最合理的安排方案并计算 \(FJ\) 可以得到的最大效率。

注意,方案需满足不能包含超过 \(K\) 只编号连续的奶牛。

输入格式
第一行:空格隔开的两个整数 \(N\)\(K\)

第二到 \(N+1\) 行:第 \(i+1\) 行有一个整数 \(E_i\)

输出格式
共一行,包含一个数值,表示 \(FJ\) 可以得到的最大的效率值。

数据范围
\(1≤N≤10^5,0≤E_i≤10^9\)

输入样例

5 2
1
2
3
4
5

输出样例

12

样例解释
\(FJ\)\(5\) 只奶牛,效率分别为 \(1、2、3、4、5\)

\(FJ\) 希望选取的奶牛效率总和最大,但是他不能选取超过 \(2\) 只连续的奶牛。

因此可以选择第三只以外的其他奶牛,总的效率为 \(1 + 2 + 4 + 5 = 12\)

二、暴力作法

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
typedef long long ll;
int a[N];
ll s[N], f[N];

/*
BruteForce
通过了 9/12个数据
*/
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];

    // 暴力大法
    for (int i = 1; i <= n; i++)
        for (int j = i; j >= i - m; j--)
            f[i] = max(f[i], f[j - 1] + s[i] - s[j]);

    cout << f[n] << endl;
    return 0;
}

三、动态规划

考虑用 动态规划 来求解本问题

由于 连续选择 超过 \(m\) 个元素时,这些元素的 贡献\(0\) (相当于没选)

而本题,所有的元素值都是 正整数,故我们的方案中,连续选择的元素数量 一定是 不超过 \(m\)个的

状态表示

\(f[i]\):以第\(i\)个数为结尾的所有数字中,连续长度不超过\(m\),贡献总和最大值

我们来分析一下这个最优状态可能由哪些状态转移而来。

举个栗子

假设\(m=3\),现在讨论一下第\(6\)头牛,它这个位置有两种选择,选与不选:

  • 不选:由于第\(6\)头没有发挥作用,现在的 最大贡献和前面的最大贡献和一致:

\[\large f[6]=f[6-1]=f[5] \]

  • 选择:如果它选了,那么它前面的就不是随意的了,需要有范围限制,人脑模拟一下:
    最大连续选择长度不能超过\(3\),所以讨论以下情况:
    • 假设本轮取\(3\)个最优:

      \[\large f[6]=a[6]+a[5]+a[4]+ ? \]

    • 假设本轮取\(2\)个最优:

      \[\large f[6]=a[6]+a[5]+?' \]

    • 假设本轮取\(1\)个最优:

      \[\large f[6]=a[6]+?'' \]

注:不是能取\(3\)个取\(3\)个值就最大了,因为你取了\(3\)个,意味着前面第\(4\)个就不能取,这合不合适就不一定了

为啥要加\(?\)呢?以\(3\)个为例,如果取\(3\)个最优,那么此时取到了\(4\)号结点位置,\(3\)号肯定是不能取的!原因很简单,如果取上,就是连续\(4\)个了,超过了\(m\)限定!那\(3\)左侧是随意的,这时,\(f[2]\)的含义是在前\(2\)个元素中合法并可以获取到的最大值,现在的情况可以直接表示为\(f[2]\),同理得到:

\(\large f[6]=a[6]+a[5]+a[4]+f[2] \\ \large f[6]=a[6]+a[5]+f[3] \\ \large f[6]=a[6]+f[4]\)

\(a[6]+a[5]+a[4]\)这样的东东,很显然是前缀和的基本表示式,提示我们引入前缀和进行思考。如果预处理了前缀和,那么就有:

利用 前缀和 思路优化一轮:

\(\large f[6]=s[6]-s[3]+f[2]\)
\(\large f[6]=s[6]-s[4]+f[3]\)
\(\large f[6]=s[6]-s[5]+f[4]\)

通用化处理一下:(\(6\)就是固定的当前值\(i\),而\(3,4,5\)是可变的,我们设为\(j\),同时\(j\)是有范围的,也就是\(i-m \sim i-1\))

\[\large f[i]=s[i]-s[j]+f[j-1] ~~~ j \in [i-m,i-1] \]

\(i\)确定的情况下,\(s[i]\)是固定的,变化的就是\(f[j-1]-s[j]\),要想求\(f[i]\)最大值,就是求\(f[j-1]-s[j]\)的最大值,而\(j\)是有范围的,这就转化为一个区间内取极大值问题,可以 用单调队列来优化

单调队列优化办法

  • ① 维护一个队列,记录距离\(i\)前面的\(m\)个区间范围内,\(f[j-1]-s[j]\)取值最大

  • ② 这个最优的序号\(j\),就是队列头的记录序号\(q[hh]\)

  • ③ 因为队列中不包含\(i\)结点,所以需要在\(i\)进入队列前,\(while\)上方进行计算更新结果

时间复杂度

\(O(n)\)

四、实现代码

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
typedef long long LL;
int q[N];
LL s[N], f[N];

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];

    // 由于滑动窗口在i的左边,需要讨论前缀和的s[l-1],第1个没有前序,不符合整体的代码逻辑,添加了一个哨兵
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i++) {
        //  1、年龄大于m的老家伙们,不管是不是实力够强大,一概去死
        while (hh <= tt && i - q[hh] > m) hh++;

        // 因为滑动窗口在i 左侧,先使用再加入
        f[i] = max(f[i - 1], f[max(0, q[hh] - 1)] + s[i] - s[q[hh]]);

        //  2、不如我年轻,并且,不如我有实力的老家伙们去死
        while (hh <= tt && f[i - 1] - s[i] >= f[max(0, q[tt] - 1)] - s[q[tt]]) tt--;

        // 3、i入队列
        q[++tt] = i;
    }
    // 输出结果
    cout << f[n] << endl;
    return 0;
}
posted @ 2022-01-17 08:25  糖豆爸爸  阅读(167)  评论(0编辑  收藏  举报
Live2D