单调队列优化动态规划

前置知识:单调队列

1.1例题

例题1:琪露诺
常见思路:首先容易推出朴素转移方程:
fi 表示琪露诺在在 i 格时累计能获得多少冰冻指数,fi=ai+minirjilfj,时间复杂度为 O(n2),考虑优化。
容易发现 fjj 的限制就像是一个滑动窗口,所以可以用单调队列优化。

操作:①令 h=1t=0
②遍历到一个新的 i
③先更新队尾:将在新元素(指能转移到当前 fi 的元素却无法转移到 fi1 的元素)前面且不比它优的元素从队尾出队,再加入新元素。
④再更新队头:如果队头超出长度限制,从队头出队。
⑤这时已经保证了单调队列中有 irjil 的所有 有可能有用的 j(即有可能转移到后面的 j)都在队列里且有单调性,此时队头为最优转移的位置,用队头更新 fi 即可。
时间复杂度:O(n)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,l,r,f[200010],a[200010],q[200010],ans=-0x7fffffff;
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>l>>r;
	for(int i=0;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)f[i]=-0x7fffffff;
	for(int i=l,h=1,t=0;i<=n;i++){
		while(f[q[t]]<=f[i-l]&&h<=t)t--;
		q[++t]=i-l;
		while(q[h]+r<i)h++;
		f[i]=f[q[h]]+a[i];
		if(i+r>n)ans=max(ans,f[i]);
	}
	cout<<ans<<'\n';
	return 0;
}

例题2:[NOIP2017 普及组] 跳房子
由于题目要求满足某个条件至少要花多少金币,而且容易发现 x 个金币可以做到的事 x+1 个金币也可以,所以考虑用二分解决。
如何判断花了 g 枚金币能否获得至少 k 分?求出花了 g 枚金币最多能获得多少分即可。
考虑用动态规划,可以令 fi 表示小 R 在第 i 格时累计能获得多少分数,得到转移方程:fi=si+maxxigdxjmin(xi1,xi+gd)fj
动态规划的时间复杂度为 O(n2),考虑优化。
操作:①令 h=1t=0
②遍历到一个新的 i
③先更新队尾:将在新元素(指能转移到当前 fi 的元素却无法转移到 fi1 的元素)前面且不比它优的元素从队尾出队,再加入新元素。但是注意这里的新元素可能不止 1 个,所以要whilewhile,将所有新元素判断并入队。
④再更新队头:如果队头超出长度限制,从队头出队。
⑤这时已经保证了单调队列中有 xigdxjmin(xi1,xi+gd) 的所有有可能有用的 j(即有可能转移到后面的 j)都在队列里且有单调性,此时队头为最优转移的位置,用队头更新 fi 即可。
动态规划的时间复杂度:O(n),可以通过此题。

点击查看代码
#include<bits/stdc++.h>
using namespace std; 
typedef long long ll;
ll l=0,r=0,s=0,n,d,k,f[500010],q[500010],a[500010],x[500010],ans=0;
bool ch(ll p){
	memset(f,-0x3f3f3f3f3f3f3f3f,sizeof(f));
	memset(q,0,sizeof(q));
	f[0]=0;
	ll res=-0x3f3f3f3f3f3f3f3f;
	for(ll i=1,j=0,h=1,t=0;i<=n;i++){
		while(j<i&&x[i]-x[j]>=d-p){
			while(h<=t&&f[q[t]]<=f[j])t--;
			q[++t]=j++;
		}
		while(h<=t&&x[i]-x[q[h]]>d+p)h++;
		if(h<=t)f[i]=f[q[h]]+a[i];
		res=max(res,f[i]);
	}
	return res>=k;
}
int main(){
	cin>>n>>d>>k;
	for(ll i=1;i<=n;i++){
		cin>>x[i]>>a[i];
		if(a[i]>0)s+=a[i];
		r=max(r,x[i]);
	}
	if(s<k){cout<<-1<<'\n';return 0;}
	while(l<=r){
		ll mid=(l+r)>>1;
		if(ch(mid))ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans<<'\n';
	return 0;
}

例题3:P6040
这一题有些复杂,但是看完题后还是可以列出转移方程:令 fi 表示在辅导第 i 个同学时在前 i 个同学身上花费的最小精力,则枚举上一个辅导的同学 j ,表示 fi=ai+minixj<ifj+(ij1)×d+k
这里好像让人无从下手,但是我们充分发扬乱搞推式子精神,拆开发现式子等同于 fi=k+ai+(i1)×d+minixj<ifjj×d。那我们可以把 fjj×d 作为一个整体,让单调队列维护的是 fjj×d 单调递减的 j 即可。
注意,这里之所以先h++是因为在所有操作开始前已经将 1 塞进了队列里。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
	int x=0,flag=1;char c;
	while((c=getchar())<'0'||c>'9')if(c=='-')flag=0;
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return flag?x:-x;
}
ll n,k,d,x,tp,Seed,h=1,t=0,q[10000010],f[10000010];
inline int rnd(){
	static const int MOD = 1e9;
	return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}
int main(){
	n=read();k=read();d=read();x=read();tp=read();
	if(tp){
		Seed=read();
		f[1]=rnd();q[++t]=1;
		for(ll i=2;i<=n;i++){
			ll qwq=rnd();
			while(h<=t&&q[h]<i-x)h++;
			f[i]=f[q[h]]+k+(i-q[h]-1)*d+qwq;
			while(h<=t&&f[q[t]]-q[t]*d>=f[i]-i*d)t--;
			q[++t]=i;
		}
		cout<<f[n]<<'\n';
	}else{
		f[1]=read();q[++t]=1;
		for(ll i=2;i<=n;i++){
			ll qwq=read();
			while(h<=t&&q[h]<i-x)h++;
			f[i]=f[q[h]]+k+(i-q[h]-1)*d+qwq;
			while(h<=t&&f[q[t]]-q[t]*d>=f[i]-i*d)t--;
			q[++t]=i;
		}
		cout<<f[n]<<'\n';
	}
	return 0;
}

总结:单调队列优化动态规划适用于式子可以化为 fi=q(i)+maxl(i)jr(i)fj+p(j) ,并且满足 l(i)r(i) 都单不减的 1D/1D 型动态规划。
有三个步骤:入队、出队、转移。

1.2 习题

咕咕咕

posted @   lrxQwQ  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示