题目
洛谷——P2627 [USACO11OPEN] Mowing the Lawn G
Acwing——1087. 修剪草坪
题解
参考博客:AcWing 1087. 修剪草坪 【单调队列优化DP】 c++详细题解
前言:
核心:状态转移方程式的推导。
这道题的单调队列优化是针对dp的优化,是根据状态转移方程而制定的优化。
将推导出来的方程进行化简,然后用单调队列维护其中一个元素的最大值,简化转移方程中的元素的获取方式,加速转移方程的进行。
状态表示:
f[i]
表示从前i头牛中选,且合法的所有方案集合中的最大效率值。
s[i]
表示从第1头牛到第i头牛的效率和。
转移方程的推导:
对于每头牛i来说,有选和不选两种方案:
- 不选第i头牛:
f[i] = f[i-1];
- 选第
i
头牛:
按照连续长度划分为:长度为1,长度为2,长度为3……长度为k。
当连续长度为j(1<=j<=k)
时,算上第i
头牛往前数j-1
位都要出工,第i-j
头牛不算在里面而后面第i-j+1
到第n
头牛全部选上!(这点很重要!!!)
得到方程:
f[i]= f[i-j-1]+s[i]-s[i-j];
选取效率最大的情况,得到状态转移方程式:
f[i]=max(f[i-1],f[i-j-1]-s[i-j]+s[i]) (1<=j<=k)
单调队列优化之处:
- 注意
i-j
在f[i-j-1]
和s[i-j]
两个数组里面都有出现,
所以用g(i-j)=f[i-j-1]-s[i-j]
来简化公式:
f[i]= g(i-j)+s[i] (1<=j<=k)
。- 继续对
g(i-j)
进行分析:
用x
替换一下i-j
,得到(g(x)=f[x-1]-s[x]
,
此时不再有j
影响f[i]
,省去了枚举j
的循环。x
的含义:
因为i-j
的含义是第i-j
头牛不选,
所以g(x)
表示第x
头牛不选在里面时f[x-1]-s[x]
的值。- 对
g(x)
的选择进行优化:
用一个滑动窗口,窗口长度为k
,用其中元素加入单调递减队列中,维护其单调性,保证当前队头的f值
最大。x
的范围分析:
x
等于i-k
时,第i-k
头牛是不选的,连续的牛刚好是k
头,所以x==i-k
满足题目条件。
那么x
范围就是x>=i-k
。- 先出队用前面元素排列后得到的
g(q[st])
处理出f[i]
后,再将g(i)
入队。因为自身的g(i)
不能对自己的f[i]
产生影响。
具体操作:
- 处理出前缀和数组
s[N]
i
从第一头牛开始枚举到第n
头牛,每次循环得到最优结果f[i]
。- 对于每次结果,使用动态转移方程:
f[i]=max(f[i-1],g(i-j)+s[i]); (1<=j<=k)
。
出队:当前f[i]
的决策依赖于队伍q
的队头q[st]
的g
值g(q[st])
,所以先出队并使用之前的队头更新f[i]
值。
入队:g(x)=f[x-1]-s[x]
此时将g(i)
加入。
实际操作遇到的一个问题:
- 发现问题:初始化的时候必须是
st==ed;
,ed<st
会出错。- 分析问题:
1.一开始肯定是全部牛的效率都加上的,直到连续的牛的数量超过k
。
连续的牛超过k
我们才考虑放弃一些牛以中断这个连续。
2.选择g(x)
就意味着从x+1
到i
都要选上。
那么g(0)
按照定义解释是:第0
头牛不选而后面第1~i
头牛都选上。
而第0
头牛不存在,其实g(0)
就表示:第1~i
头牛都选上。
3.举例:i=2 (k>2)
时,最优的决策中是不可能会选择g(1)
,即不选第一头牛的。
但此时队伍里面就g(1)
一个选择,没得选,只能放弃第一头牛的效率,这样就出错了 。- 解决问题:
加入第0
头牛,让它返回g(0)=0
,来提供“第1~i
头牛都选上”的选择。
这样一来,队伍里面就已经有了一个元素0
,就必须是st==ed
了。
代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e6 + 10;
ll f[N], s[N];
int n, k, st = 0, ed = 0, q[N];
ll g(int x) {
if(x == 0)
return 0;
return f[x - 1] - s[x];
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++ ) {
scanf("%lld", &s[i]);
s[i] += s[i - 1];
}
for(int i = 1; i <= n; i ++) {
//出队
while(st <= ed && q[st] < i - k)
st ++;
f[i] = max(f[i - 1], g(q[st]) + s[i]);
//入队
while(st <= ed && g(i) >= g(q[ed]))
ed --;
q[++ ed] = i;
}
printf("%lld", f[n]);
return 0;
}