AcWing 1087. 修剪草坪(背包模型 + 单调队列优化)

news/2024/7/3 2:10:49

AcWing 1087. 修剪草坪(背包模型 + 单调队列优化DP)

  • 一、问题
  • 二、分析
    • 1、思路
    • 2、状态表示
    • 3、状态转移
  • 4、单调队列优化
  • 三、代码

一、问题

在这里插入图片描述

二、分析

1、思路

这道题其实类似于背包问题,有两处不同点,一个是本题没有体积的限制,也就是说没有规定我们选多少牛。第二个就是我们选的牛中,不能有 k + 1 k+1 k+1个及以上头牛是相邻的。

那么既然类似于背包问题的话,我们既可以根据第 i i i个选或者不选来写方程。

2、状态表示

f [ i ] f[i] f[i]表示从前 i i i头牛中选,所能达到的最大效率。

3、状态转移

由于我们的 f [ i ] f[i] f[i]是最大值,也就是说我们需要找出所有能转移到 f [ i − 1 ] f[i-1] f[i1]的表达式,然后在所有的表达式中比较出一个最大值。

如果画成图的话,可以表示为下图:
在这里插入图片描述

上图的两种情况就是所有能转移到 f [ i ] f[i] f[i]的情况。

对于不选 i i i的情况,我们可以表示为 f [ i − 1 ] f[i-1] f[i1]

但是对于选 i i i的情况,我们很不好表示。

因为我们选了第 i i i头牛的时候,还需要考虑和它相邻的是否超过了 k k k个,也就是说我们无法用一种情况去表示出右面。

那么究其原因就是因为右边的划分过于宽泛,以至于无法用一个转移式子直接表示出来。

因此,我们需要对右边继续细分。

我们的状态定义是前 i i i个牛中选的最大效率。也就是说 i i i是最后一个牛。

那么能和第 i i i个牛连续的,都在 i i i的左边。

不能超过 k k k个连续的,也就是说最多1,2,3,4,…,k个牛连续。

我们可以按照连续的牛的个数对右侧的集合继续划分。

在这里插入图片描述
如上图所示,那么我们假设和 i i i被连续选中的牛有 j j j个,这种情况如何表示呢?

我们看下面的图:

在这里插入图片描述
对于黑色区域中,是我们必定选的 j j j个连续的牛。

那么现在的关键就是求出这个区间内的牛的效率的和。为了快速的求出来,我们可以使用前缀和。

也就是 s [ i ] − s [ i − j ] s[i]-s[i-j] s[i]s[ij]

由于蓝色部分肯定不选,所以不用管他。

红色部分则需要用到子问题的最优解, f [ i − j − 1 ] f[i-j-1] f[ij1]

那么我们 f [ i ] f[i] f[i]中连续选 j j j头牛的情况就可以表示为:
f [ i ] = f [ i − j − 1 ] + s [ i ] − s [ i − j ] f[i]=f[i-j-1]+s[i]-s[i-j] f[i]=f[ij1]+s[i]s[ij]

我们的 j j j的范围是 1 ≤ j ≤ k 1 \leq j \leq k 1jk的。

我们只需要枚举出每一个 j j j的情况,然后求一个最大值。

f [ i ] = m a x ( f [ i − j − 1 ] + s [ i ] − s [ i − j ] ) f[i]=max\bigg(f[i-j-1]+s[i]-s[i-j]\bigg ) f[i]=max(f[ij1]+s[i]s[ij])

但这个最大值不一定是当前状态的最优解,这个只是刚刚的集合中右半部分的最大值,我们还需要和左半部分的 f [ i − 1 ] f[i-1] f[i1]做一个比较,选择最大的。

那么我们考虑一下边界情况。

第一个就是要保证 i − j i-j ij大于等于0,这个情况对应的实际意义如下:

我们的 j j j i i i左边的连续的 j j j头牛,但是有可能我们的 i i i比较小,所以不足以提供 j j j头牛。

那么这种情况下我们最多选 i i i头,只需要写成 s [ i ] − s [ 0 ] s[i]-s[0] s[i]s[0]即可。

那么还有一个边界情况。就是我们的 i − j − 1 ≥ 0 i-j-1\geq 0 ij10

这个边界情况对应的实际意义是:可能我们的左边有 j j j头牛,我们算上 i i i需要再选 j − 1 j-1 j1头牛,也就是说此时只剩下最左边的那一头牛。这时候,这头牛肯定是不选的,即我们刚刚图中的蓝色点。

这种情况下我们的 f [ i − j − 1 ] f[i-j-1] f[ij1]是不存在的,因为最左边的牛的左边没有牛了。此时我们只需要加上 0 0 0即可。

i − j − 1 i-j-1 ij1是负数的时候,其实也是蓝色点左侧没有牛的情况,也是替换成0,那么为了方便,直接将 f [ 0 ] f[0] f[0]初始化为0,然后直接加 f [ 0 ] f[0] f[0]即可。

我们此时就能写出代码:


#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
ll a[N];
ll f[N];
void solve()
{
	int n, k;
	cin >> n >> k;
	for(int i = 1; i <= n; i ++ )
	{
		scanf("%lld", a + i);
		a[i] += a[i - 1];
	}
	for(int i = 1; i <= n; i ++ )
	{
		f[i] = f[i - 1];
		for(int j = 1; j <= k; j ++ )
		{
		    if(i - j < 0)
		    {
			    f[i] = max(f[0] + a[i] - a[0] , f[i]);
		    }
		    else if(i - j < 1)
			    f[i] = max(f[0] + a[i] - a[i - j], f[i]);
			else
			    f[i] = max(f[i - j - 1] + a[i] - a[i - j], f[i]);
		}
	}
	cout << f[n] << endl;
}
int main()
{
	solve();
	return 0;
}

但是很明显,这样做的时间复杂度是 O ( n ∗ k ) O(n*k) O(nk)的,很容易超时。

那么怎么办呢?

我们看下面的优化方案:

4、单调队列优化

在这里插入图片描述
根据上面图片的推导, j j j的范围是固定的,那么 i − j i-j ij的范围长度也是固定的,但是 i i i在改变,所以这个区间范围在向右边挪动。

也就是说,这是一个滑动窗口求最值得问题。

单调队列的复杂度均摊到每一次的操作中是 O ( 1 ) O(1) O(1)的,那么总的时间复杂度就是 O ( n ) O(n) O(n)

我们还需要注意一个边界问题,对于任意的 x x x,我们构造的 g g g函数是
g ( x ) = f [ x − 1 ] − s [ x ] g(x)=f[x-1]-s[x] g(x)=f[x1]s[x]

如果 x = 0 x=0 x=0的话,我们的 f [ i − 1 ] f[i-1] f[i1]的下标就越界了。

此时我们需要考虑一下实际意义特判一下,如果 x x x是0,即 i − j i-j ij是0,而 i − j i-j ij是我们分析的蓝色点,说明蓝色点的左边没有牛,也就是说 f [ i − j − 1 ] f[i-j-1] f[ij1]等于0,而 s [ 0 ] s[0] s[0]也是0,所以当 x = 0 x=0 x=0的时候,返回 0 0 0就行。

还需要考虑一个问题,一开始的时候队列是空的,也就是我们的 i i i等于的时候,此时元素只有一个,那么最大效率就是 s [ 1 ] s[1] s[1],就是说前面的那一项是 g [ 0 ] g[0] g[0],所以我们要先入队一个 0 0 0

三、代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll f[N], s[N];
ll q[N], hh, tt = -1;
ll g(int x)
{
	if(!x) return 0;
	else return f[x - 1] - s[x];
}
void solve()
{
	int n, k;
	cin >> n >> k;
	
	for(int i = 1; i <= n; i ++ )
	{
		scanf("%d", s + i);
		s[i] += s[i - 1];
	}

	q[++tt] = 0;
	
	for(int i = 1; i <= n; i ++ )
	{
		if(hh <= tt && q[hh] < i - k )hh ++;
		while(hh <= tt && g(q[tt]) <= g(i))tt --;
		f[i] = max(f[i - 1], g(q[hh]) + s[i]);
		q[++tt] = i;
	}
	cout << f[n] << endl;
}
int main()
{
	solve();
}

http://lihuaxi.xjx100.cn/news/821453.html

相关文章

华为机试题:HJ40 统计字符(python)

文章目录博主精品专栏导航知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。1.1、input()与list(input())的区别、及其相互转换方法2、print() &#xff1a;打印输出。3、str.isdigit()&#xff0c;str.isnumeric()&am…

如何批量删除word中的中文和标点符号(word删除中文所有标点符号)

如何批量删除word中的中文和标点符号&#xff08;word删除中文所有标点符号&#xff09; 当文档中前面一列英文&#xff0c;后面一列汉字的时候&#xff0c;你还在一个一个的去删除汉字吗&#xff1f;那样也太慢了。快快看看下面介绍的几种方法&#xff0c;绝对会大大提高你的…

requests库登录网站,Session()和session()差一个大小写非常要命

笔者最近用Python爬取网站&#xff0c;首页需要输入用户名和密码&#xff0c;由于该网站不需要验证码&#xff0c;登录步骤比较简单。用selenium的webdriver打开Chrome浏览器实现自动化来登录&#xff0c;代码不难写而且登录很顺利。后来再想&#xff0c;selenium打开浏览器比较…

JS数据类型有哪些?区别是什么?

JS数据类型有哪些&#xff1f;首先JS数据类型有Number、String、Boolean、BigInt、Symbol、Null、Undefined、Object、8种。其次JS数据类型又分为两类&#xff1a;一类是基本数据类型也叫做简单数据类型。包含7种类型&#xff1a;Number 、String、Boolean、BigInt、Symbol、Nu…

React:有关a标签控制台警告的一些问题

近几日在写react项目的时候&#xff0c;发现了一些问题&#xff0c;特此记录&#xff01; 目录 1.控制台警告信息&#xff0c;由target"_blank"引起的问题 2.由href""引起的问题 1.控制台警告信息&#xff0c;由target"_blank"引起的问题 Usi…

一篇掌握分布式锁

分布式锁理解 1.业务场景引入 在进行代码实现之前&#xff0c;我们先来看一个业务场景&#xff1a; 系统A是一个电商系统&#xff0c;目前是一台机器部署&#xff0c;系统中有一个用户下订单的接口&#xff0c;但是用户下订单之前一定要去检查一下库存&#xff0c;确保库存足…

一篇了解SSO单点登录

SSO基础 文章目录SSO基础1.什么是单点登录&#xff1f;2.回顾普通系统登录3.多系统登录的问题与解决&#xff1f;3.1.Session不共享问题XXL-SSO框架基础入门1.什么是XXL-SSO2.特性3. 官方Demo分析3.1 SSO Server中央认证服务3.2 SSO Client应用&#xff08;Cookie形式&#xff…

XSS Challenges

XSS 挑战 (由 yamagata21) - 阶段 #1 (int21h.jp) 题目要求注入 JavaScript 命令: alert(document.domain); Stage #1 输入321来定位代码的位置,发现是处于<b></b>标签之内,没有任何过滤 // 第一种方法是闭合 b 标签,插入 Script 标签 "</b> <…