单调队列优化dp

前置知识:单调队列 不会的话可以去看我这一篇 单调队列

空讲太不清楚了,还是举个 栗子

可以发现,这很明显要用dp,看起来似乎跟单调队列有点关系,不过有一点区别,单调队列维护的是一段滑动窗口(就是一段连续长度,为\(k\)的子段),而这一题却是要求连续选择数不能超过\(k\)

先直接给出暴力dp的式子吧,毕竟这篇重点在单调队列优化上

\(dp_i\) 为考虑到i时满足条件的最大和

\[dp_i = \max_{max(i-k,0) \leq j \leq i} ( dp_{j-1} + sum_i - sum_j) \]

\(sum_i\)是指到 \(i\) 的前缀和
暴力枚举间隔点j,注意j=0的时候特殊处理\(dp_{j-1}\)为0;

发现在这一题的数据量下\(n^{2}\)肯定是会\(TLE\)的,考虑来学习使用单调队列进行优化

观察这个\(dp\)式子,发现这个\(sum_i\)是可以提出来的,这样式子就可以转化为

\[dp_i = \max_{max(i-k,0) \leq j \leq i} ( dp_{j-1} - sum_j)+ sum_i \]

这个时候我们发现,\(dp_{j-1}\)\(sum_j\)是在枚举\(j\)的情况下是对应且连续的,可以理解为每一个\(i\)对应了\(j\)的一个区间,而区间之间有非常多的重合,导致我们浪费了许多是时间算算过的东西,所以,其实我们要的是\(i\)对应\(j\)区间的最大值再加上\(sum_i\),这样就转化为了单调队列可以实现的样子

代码实现

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
int a[100010];
int sum[100010];
int dp[100010];
int zhuan(int id){
	if(id==0) return 0;
	return dp[id-1]-sum[id];
}
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
	}
	deque<int> q;
	q.push_back(0);
	for(int i=1;i<=n;i++){
		int now=zhuan(i);
		while(!q.empty()&&zhuan(q.back())<now){
			q.pop_back();
		} 
		if(!q.empty()&&i-q.front()>k) q.pop_front();
		q.push_back(i);
		dp[i]=zhuan(q.front())+sum[i];
	}
	cout<<dp[n];
	return 0;
}

通过这道题目,发现要用单调队列优化\(dp\),需要对\(dp\)公式进行转化,转化为一种类似这样的形式
\(dp_i=max||min(dp_j+b_j)+a_i\)\(j\)有一段区间

而队列维护的则是括号内的内容,这段内容不能与\(i\)有关,如果既和\(i\)又和\(j\)有关,就不能在后面一步求所有的\(dp_i\)时得到应用。具体来说是这样的,如果跟两个都有关,假如说\(j\)\(j+1\)对应的\(b\)值一样,也就相当于\(i\)对队列里的值也有影响,每加2才会改变一次\(b\)值,这样的话\(dp_j\)\(b_j\)无法一一对应,每次加入值也无法只加一个,复杂度就假了

需要转化,可以将它分成余数为0和余数为1的两个单调队列,这样就又满足条件了

给个类似的题目P2627
和这个P1776

posted @ 2024-11-29 11:22  hapihapi  阅读(8)  评论(0编辑  收藏  举报