首页 > 编程语言 >【算法学习】单调队列优化dp

【算法学习】单调队列优化dp

时间:2024-11-13 15:21:15浏览次数:1  
标签:队列 sum cin long int dp 区间 单调

前言

这已经是很基础很模板化的优化了,我们可以理解为用贪心的思路去掉了永远不可能用到的状态,通常用于长度固定是个区间、最大值且满足单调性的题目。

如果一个选手比你小,还比你强,你就永远也打不过他了。这很残酷但这也是单调队列的思想,虽然现实情况比较多变。

P3572 [POI2014] PTA-Little Bird

最基础的单调队列优化题,我们暴力写出后得到:

\[f_i=\min f_k(+1(a_i>=a_k)) \ \ \ \ \ k\in [i-1,i-k] \]

我们发现这个 \(\min\) 就是满足单调性的条件,而且如果 \(f\) 值相同时我们再对比 \(a\) 的大小,将 \(a\) 更大的留下,这个过程就相当于一个排序的过程,只不过因为单调性所以我们删掉一些数也没关系。

满足单调性的条件不止有一个,所以条件都满足单调性就尽管单调队列优化。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define re register 
#define ll long long
const int N=1e6+10;
const int mod=998244353;
using namespace std;

int n,q;
int a[N];
int f[N];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	
	cin>>q;
	
	while(q--){
		int k;
		cin>>k;
		memset(f,0x3f,sizeof f);
		f[1]=0;
		for(int i=2;i<=n;i++){
			for(int j=i-1;j>=max(1ll,i-k);j--){
				if(a[j]>a[i]){
					f[i]=min(f[i],f[j]);
				}
				else{
					f[i]=min(f[i],f[j]+1); 
				}
			}
		}
		cout<<f[n]<<"\n";
	}
	return 0;
}

P1725 琪露诺

终于自己写出来的题,但确实很简单,暴力都能80分。

暴力:每个位置可以从区间 \([i-r,i-l]\) 转移过来,那就直接转移。

正解:可以发现这个区间长度是固定的,每次取出区间内的最大值转移过来,可以用滑动区间的方法维护区间值,然后就可以了。

好吧好吧还是放一下吧,点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+10;

int n,l,r;

int a[N];
int f[N];

queue<int> q;

signed main(){
    ios::sync_with_stdio(false);
	cin.tie(nullptr); 
	memset(f,-0x3f,sizeof f);
	cin>>n>>l>>r;
	
	n++;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int ans=-1e9;
	f[1]=a[1];
	
	for(int i=1;i<=n+1000;i++){
		if(!q.empty()&&q.front()<=i-l-(r-l+1)){
			q.pop();
		}
		while(!q.empty()&&f[q.front()]<f[i-l]){
			q.pop();
		}
		if(i-l>=1){
			q.push(i-l);
			int k=q.front();
			int x=f[q.front()];
			f[i]=max(f[i],a[i]+x);
		}
		if(i>=n+1){
			ans=max(ans,f[i]);
		}
	}
	cout<<ans;
    return 0;
}

P1714 切蛋糕

我会暴力,枚举位置,向后加和,复杂度 \(O(n^2)\) 太差了。

因为要求区间和最大,所以考虑维护一个前缀和,每次找到前长度m的区间内的最小值转移过来,但要用双端队列维护,如这个样例 [3,-2,4,-1,-5] 一开始要先push(0)吧,后面的前缀和都比0大所以都会堆积在最后,但当0自然弹出时,后面的数并没有降序排序,因为我们会只看sum_0来弹出而又没有比他小的,然后就寄了。

后记:才发现用queue维护都是不对的,希望以后长记性。

确实该注意
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
#define int ll
int n,m;

int a[N];
int sum[N];
int f[N];
int ans=-1e9;
list<int> q;

signed main(){
    ios::sync_with_stdio(false);
	cin.tie(nullptr); 
	
	cin>>n>>m;
	memset(f,-0x3f,sizeof f);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
	}
	q.push_back(0);
	for(int i=1;i<=n;i++){
		while(!q.empty()&&q.front()<i-m){
			q.pop_front();
		}
		f[i]=max(f[i],sum[i]-sum[q.front()]);
		ans=max(ans,f[i]);
		while(!q.empty()&&sum[q.back()]>=sum[i]){
			q.pop_back();
		}
		q.push_back(i);
	}
	cout<<ans; 
    return 0;
}

P2627 [USACO11OPEN] Mowing the Lawn G

我会暴力,枚举位置i,枚举长度j,枚举之前位置k,目前连续与之前最大连续转移过来。

优化暴力,每个奶牛只有选和不选两个状态,那直接dp这个状态,然后分类转移(\(f[i][0]\) 不选这个奶牛最大的值)。

  • \(f[i][0]=max(f[i-1][0],f[i-1][1])\)

  • \(f[i][1]=max(f[i][1],f[j-1][0]+sum[i]-sum[j]),[max(i-m,0)<=j<i]\)

然后可以愉快转移了。

可以发现下面的枚举j是是一个区间内找到最大的值,明锐察觉可以单调队列优化,好!!结束了(维护这个 \(ans=max(ans,f[j][0]-sum[j])\))。

P2629 好消息,坏消息

很离谱的一道题,我想不出来也很正常吧(逃ε=ε=ε=┏(゜ロ゜;)┛)其实想出来一点了,但假了一半,然后就无法思考了,开摆。

暴力:\(O(n^2)\) 枚举判断,竟然能得85!!!

正解:因为是环,可以拆成链,此时可以这么看数组 -3,5,1,2,-3,5,1 每个区间对于这一种情况,像 [1,2,-3,5] 对应 1->2->-3->5,然后此时计算一个区间可以用前缀和,第k个区间,找到区间内前缀和的最小值减去 \(sum[k-1]\) (因为和此时的区间没关系,我们要找到依次加和过程中的最小值)判断是否为负数,统计答案即可,找到区间前缀和最小就可以用单调队列实现。

标签:队列,sum,cin,long,int,dp,区间,单调
From: https://www.cnblogs.com/sadlin/p/18544028

相关文章

  • 区间$dp$
    区间\(dp\)特点,可由小区间加上一堆运算推到大区间(板子)或者一个序列,从中间扣掉一个/一堆点,扣掉后短处会连上,这种题也常用区间\(dp\)。(消除木块,恐狼后卫,最大收益,最小代价都是这种题),它们常要考虑删掉这段区间/点会产生的贡献,再加上外面的区间和,有时候还会开一些辅助数组或多开一个维......
  • 38、基于AT89C52的VIM-332-DP笔段式液晶动态显示proteus仿真设计
    一、仿真原理图:二、仿真效果:三、相关代码:/************************************************************************************** *FunctionName   :DisplayM *Description    : *******************************************************......
  • 常用的物联网消息队列-Mqtt协议
    EMQX和Mosquitto都是广泛使用的MQTT消息代理,但它们在设计目标、功能和适用场景上有一些显著的区别。Emqx使用教程添加依赖<dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.5</......
  • atrm——删除待执行任务队列中的指定任务
    转自于:https://github.com/jaywcjlove/linux-command,后不赘述atrm删除待执行任务队列中的指定任务补充说明atrm命令用于删除待执行任务队列中的指定任务。语法atrm(选项)(参数)选项-V:显示版本号。参数任务号:指定待执行队列中要删除的任务。实例删除已经排队的任......
  • 云消息队列 Kafka 版全面升级:经济、弹性、稳定,成本比自建最多降低 82%
    作者:娜米本文整理于2024年云栖大会阿里云智能集团产品专家张凤婷带来的主题演讲《云消息队列Kafka版全面升级:经济、弹性、稳定》云原生消息产品十年磨一剑消息产品的演进可以大致分为三个主要阶段:起步阶段:初期,市场上缺乏能够支撑大规模业务场景的优秀消息产品,无论是商......
  • [阻塞队列]
    目录1.阻塞队列2.阻塞队列的优点(1)实现服务器之间的"低耦合".(2)实现"削峰填谷"的功能.3.阻塞队列代码举例4.自己实现阻塞队列1.阻塞队列我们知道,标准库中原有的队列Queue及其子类,都是线程不安全的,所以java封装了一个名为"阻塞队列"(BlockingQueue)......
  • 基于 dp 凸性的优化策略(待修缮)
    斜率优化\(y=kx+b\)形式维护队列,询问不单调则二分决策点。SlopeTrick如果决策函数满足以下条件:连续凸包,每一段斜率为整数凸包上断点之间的一次函数斜率总和为\(\mathcalO(n)\)级别则称这个函数满足性质\(T\),且如果\(f,h\)都满足性质\(T\),则\(f+h\)也满足性质......
  • 关于分治法左右区间单调遍历应该如何设计
    阅读以下文章,首先至少要求通过一道分治法的题目或听过一道该类型的讲解。对于分治的题目,想必你应该知道,通常我们是对于一个区间拆分两个部分,而最小子问题通常是只包含一个元素的区间数组。为了后续方便处理更大范围的区间,通常在处理该小区间后,我们会将其区间内元素排序,例......
  • 决策单调性 dp
    重修!updon09/14/24:狗屁模拟赛督促我来更新这篇博。以下讨论最小化问题。对于\(n\timesn\)的矩阵,有:子矩阵\(A_{[i_1,i_2,..,i_k][j_1,j_2,..,j_\ell]}\)为矩阵\(A\)取出\(i_1,i_2,..,i_k\)行和\(j_1,j_2,..,j_\ell\)组成的矩阵。其中当序列\(i,j\)元素连续时......
  • 线程进阶篇4:如何用Executors工具类创建线程池-代码演示-源码分析-可行性分析,对比new T
        本篇文章主要是讲解如何使用Executors工具类创建线程池,看本篇之前建议同学们先去看看我发布的上一篇文章,即用newThreadPoolExecutor()来创建线程池,里面讲解了线程池的参数使用方法和场景,熟悉了之后再来学习这一篇会更容易理解一些!因为Executors只是一个工具类,底层......