一、方法1

AcWing 1087. 修剪草坪_i++


  1. 状态表示-集合:前\(i\)头牛组成的所有合法方案
  2. 状态表示-属性:\(max(sum(w_{i-k+1},...,w_{i-1},w_{i}))\)
  3. 状态计算


  • 以第\(i\)头牛选与不选来划分集合
  • 不选第\(i\)头牛
    此时,\(f[i]\)所表示的最大效率值应该是来源于前\(i-1\)头牛,所以此时\(f[i]=f[i-1]\)
  • 选第\(i\)头牛
    此时,肯定是获得了\(w[i]\)的效率值,那前面怎么表示呢?此处的方法是:既然选择了\(i\),那么最多可以选择连续几个呢?当然是\(k\)个,我们从\(i\)开始向前遍历\(k\)头牛(包含\(i\)自己),计算一个\(w[j]\)的效率值和,就可以表示出来此时的\(f[i]\)。同时我们还要小心,因为如果我们选择了第\(i-j\)头牛做为开头,那么第\(i-j-1\)头牛必然是不能选的,因为如果选了,肯定是违反了连续\(k\)个的要求!后面的连续和可能用前缀和来计算,这样就得到的转移方程:

\[f[i]=max(f[i-j-1]+s[i]-s[i-j]) \]

因为此时\(s[i]\)可以视为固定不变的,变化的是\(j\),所以方程变形为:

\[f[i]=max(f[i-j-1]-s[i-j])+s[i] \]

此时问题就比较清楚了,就是当目前讨论第\(i\)头牛时,向前\(k\)位,查找一个位置\(j\),使得\(f[i-j-1]-s[i-j]\)值最大。暴力法用循环去二次遍历查找肯定是可以得到的,联想到上一题的优化思路,也可以尝试使用单调队列优化一下,此时单调队列中保存的依然是某个序号,但序号的含义是\(f[i-j-1]-s[i-j]\)最大的单调队列,队头就是当前的最优解,拿来即用就好啦,不用再二次循环挨个去找了~!

这里要处理一个边界问题为\(g(0) = f[-1] + s[0]\), 此时\(f[-1]\)的值应该是多少呢? 可以看出,当\(x\)为\(0\)时,也就是\(i == j\),说明当前序列长度恰好等于\(j\),那此时的最大效率自然就是所有数字的和,因此\(g(0) = 0\)

总结:


  • 先想好暴力法怎么解,再思考有哪些地方是重复计算,是否可以优化。
  • 某个区间内这样的字样的,可考虑采用单调队列进行优化。
  • 单调队列问题需要在固定\(i\)情况下讨论\(j\)的变化,此时视\(i\)不变,为常量。
  • 单调队列有时对比的并不是一个一眼可见的明显概念,而是一个计算公式。
  • 其实,不管是明显概念,还是计算公式,都可以理解为对单调队列进行维护的一个条件。
  • 单调队列问题要注意边界,思考一下现实中的含义,加上特判。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
LL f[N];
LL s[N];
int q[N];

//计算在位置i时的一个函数值
LL g(int i) {
if (i == 0) return 0;
return f[i - 1] - s[i];
}

int main() {
int n, k;
cin >> n >> k;
//前缀和
for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];

int hh = 0, tt = 0;
for (int i = 1; i <= n; i++) {
if (q[hh] < i - k) hh++;
//不选i与选i的两个状态转移方程
f[i] = max(f[i - 1], g(q[hh]) + s[i]);//在i点左侧k个范围内,找出一个j,使得g函数取值最大
while (hh <= tt && g(q[tt]) <= g(i)) tt--;
q[++tt] = i;
}
//输出
printf("%lld\n", f[n]);
return 0;
}

二、方法2

可以看出在该问题中,在序列长度大于\(k\)的情况下,最终肯定会有一部分牛没有被选,并且没有被选的牛的效率是最低的,这启发我们可以通过计算最小的损失效率来完成这道题,具体的分析过程如下。

AcWing 1087. 修剪草坪_i++_02

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1e5 + 5;

int q[N];
int n, m;
LL ans;
LL f[N];

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

int tt = 0, hh = 0;
for (int i = 1; i <= n; i++) {
if (i - q[hh] > m + 1)hh++;
f[i] += f[q[hh]];
while (tt >= hh && f[q[tt]] >= f[i])tt--;
q[++tt] = i;
}
// 长整的最大值
LL res = LLONG_MAX;
//在最后的m个牛中,都可能是累加的效率值最小的结束位置,还需要一次遍历
for (int i = n - m; i <= n; i++) res = min(res, f[i]);
//输出
printf("%lld", ans - res);
return 0;
}