单调队列优化DP

本文介绍了单调队列的概念,强调其在程序中的独特作用,队列保持单调递增或递减,并用于优化动态规划问题。通过维护队尾和队头的单调性,实现了降低DP维数,提高效率。并以粉刷木板问题为例,展示了单调队列在求解最大报酬问题中的应用。
摘要由CSDN通过智能技术生成

一、单调队列

单调队列,即单调递减或单调递增的队列。使用频率不高,但在有些程序中会有非同寻常的作用。——百度百科

重点在哪里?

使用频率不高 单调递减或单调递增的队列。

单调队列是一个双端队列,一般来说队尾用于维护单调性,队头用于保持队列大小。
单调队列有两个性质:

  1. 队列中的元素在原来列表中的顺序是单调递增的。
  2. 队列中的元素大小是单调递增或递减。

二、实现

我们将单调队列看做是一个大小一定的窗口,在列表中滑动,维护以上两个性质。
我们将单调队列的维护分为两步:

  1. 维护队尾。比较列表当中的当前元素与队尾元素,若当前元素更优,则弹出队尾,直到插入当前元素可以满足单调性时,将当前元素插入队尾。

  2. 维护队头。将数组下标在窗口之外的弹出。

例题:洛谷 P1886 滑动窗口 /【模板】单调队列

#include<iostream>
#include<cstdio>
#define MAXN 1000010
using namespace std;
int n,k;
int a[MAXN];
int q[MAXN],p[MAXN],head,tail;
void get_min()
{
	head=1;
	tail=0;
	for(int i=1;i<=n;i++)
	{
		while(tail>=head&&q[tail]>a[i])
			tail--;
		q[++tail]=a[i];
		p[tail]=i;
		while(p[head]<=i-k)
			head++;
		if(i>=k)
			printf("%d ",q[head]);
	}
	printf("\n");
}
void get_max()
{
	head=1;
	tail=0;
	for(int i=1;i<=n;i++)
	{
		while(tail>=head&&q[tail]<a[i])
			tail--;
		q[++tail]=a[i];
		p[tail]=i;
		while(p[head]<=i-k)
			head++;
		if(i>=k)
			printf("%d ",q[head]);
	}
	printf("\n");
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	get_min();
	get_max();
	return 0;
 } 

三、应用:优化DP

因为单调队列的单调性,所以我们可以用它来进行降低DP维数,进行时间和空间的优化。
例题:粉刷木板

题目描述
N N N 块木板从左到右排成一行,有 M M M 个工匠对这些木板进行粉刷,每块木板至多被粉刷一次。
i i i 个木匠要么不粉刷,要么粉刷包含木板 S i S_i Si 且长度不超过 L i L_i Li 的连续的一段木板,每粉刷一块可以得到 P i P_i Pi 的报酬。不同工匠的 $ i _i i不同。 请问如何安排能使工匠们获得的总报酬最多。

输入格式
第一行包含两个整数 N N N M M M
接下来 M M M 行,每行包含三个整数 L i , P i , S i L_i,P_i,S_i Li,Pi,Si

输出格式
输出一个整数,表示结果。

样例输入
8 4
3 2 2
3 2 3
3 3 5
1 1 7
样例输出
17

数据范围与提示
对于 100 % 100\% 100%的数据, 1 ≤ N ≤ 16000 1≤N≤16000 1N16000 1 ≤ M ≤ 100 1≤M≤100 1M100 1 ≤ P i ≤ 10000 1≤P_i≤10000 1Pi10000

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
struct node
{
	int l,p,s;
}
a[110];
bool cmp(node a,node b)
{
	return a.s<b.s;
}
int q[20000],head,tail;
int f[110][20000];
void dp()
{
	for(int i=1;i<=m;i++)
	{
		head=1,tail=0;
		for(int k=max(a[i].s-a[i].l,0);k<=a[i].s-1;k++)
		{
			while(head<=tail&&f[i-1][q[tail]]-q[tail]*a[i].p<=f[i-1][k]-k*a[i].p)
				tail--;
			q[++tail]=k;
		}
		for(int j=1;j<=n;j++)
		{
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(j>=a[i].s)
			{
				while(head<=tail&&j-q[head]>a[i].l)
					head++;
				if(head<=tail)
					f[i][j]=max(f[i][j],f[i-1][q[head]]+(j-q[head])*a[i].p);
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s);
	sort(a+1,a+m+1,cmp);
	dp();
	printf("%d",f[m][n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值