[题] 修剪草坪 #dp #单调队列优化dp

题目

洛谷——P2627 [USACO11OPEN] Mowing the Lawn G
Acwing——1087. 修剪草坪


题解

参考博客:AcWing 1087. 修剪草坪 【单调队列优化DP】 c++详细题解

前言:
核心:状态转移方程式的推导。
这道题的单调队列优化是针对dp的优化,是根据状态转移方程而制定的优化。
将推导出来的方程进行化简,然后用单调队列维护其中一个元素的最大值,简化转移方程中的元素的获取方式,加速转移方程的进行。

状态表示:
f[i]表示从前i头牛中选,且合法的所有方案集合中的最大效率值。
s[i]表示从第1头牛到第i头牛的效率和。

转移方程的推导:
对于每头牛i来说,有选和不选两种方案:

  1. 不选第i头牛:f[i] = f[i-1];
  2. 选第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)

单调队列优化之处:

  1. 注意i-jf[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)
  2. 继续对g(i-j)进行分析:
    x替换一下i-j,得到(g(x)=f[x-1]-s[x]
    此时不再有j影响f[i],省去了枚举j的循环。
  3. x的含义:
    因为i-j的含义是第i-j头牛不选,
    所以g(x)表示第x头牛不选在里面时f[x-1]-s[x]的值。
  4. g(x)的选择进行优化:
    用一个滑动窗口,窗口长度为k,用其中元素加入单调递减队列中,维护其单调性,保证当前队头的f值最大。
  5. x的范围分析:
    x等于i-k时,第i-k头牛是不选的,连续的牛刚好是k头,所以x==i-k满足题目条件。
    那么x范围就是x>=i-k
  6. 先出队用前面元素排列后得到的g(q[st])处理出f[i]后,再将g(i)入队。因为自身的g(i)不能对自己的f[i]产生影响。

具体操作:

  1. 处理出前缀和数组s[N]
  2. i从第一头牛开始枚举到第n头牛,每次循环得到最优结果f[i]
  3. 对于每次结果,使用动态转移方程:
    f[i]=max(f[i-1],g(i-j)+s[i]); (1<=j<=k)
    出队:当前f[i]的决策依赖于队伍q的队头q[st]gg(q[st]),所以先出队并使用之前的队头更新f[i]值。
    入队:g(x)=f[x-1]-s[x]此时将g(i)加入。

实际操作遇到的一个问题:

  1. 发现问题:初始化的时候必须是st==ed;ed<st会出错。
  2. 分析问题:
    1.一开始肯定是全部牛的效率都加上的,直到连续的牛的数量超过k
    连续的牛超过k我们才考虑放弃一些牛以中断这个连续。
    2.选择g(x)就意味着从x+1i都要选上。
    那么g(0)按照定义解释是:第0头牛不选而后面第1~i头牛都选上。
    而第0头牛不存在,其实g(0)就表示:第1~i头牛都选上。
    3.举例:i=2 (k>2)时,最优的决策中是不可能会选择g(1),即不选第一头牛的。
    但此时队伍里面就g(1)一个选择,没得选,只能放弃第一头牛的效率,这样就出错了 。
  3. 解决问题:
    加入第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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值