单调队列--优化dp

单调队列的定义:一种严格单调的队列(严格递增或者递减),其中队列中元素的位置也有一定的递增递减的性质。队首位置保存的是最优解,接着次优解。。


单调队列的维护(假设队列为严格递增队列):

1.新插入一个数到队尾(此时还没有插入),比较当前队尾的数和当前插入的数,如果当前插入的数小于队尾的数,弹出队尾的数。。。继续比较队尾的数和当前插入的数,直到队尾的数小于当前插入的数为止,在队尾插入当前的数


2.更新队首的数。队首的数是最优解,但是要保证队尾和队首的数在原数组中的距离不大于题意中的K(即要求最优解在当前枚举点的前K的区间内),如不符合则弹出队首,直到队首满足为止。


单调队列模板题:

poj2823


题意:求出i到i+k的所有区间中的最小和最大值,其中1 <= i <= n - k + 1。

#include<cstdio>
#include<cstring>
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,b,c) for(int a = b;a <= c;a++)
using namespace std;
typedef long long ll;
const int maxn = 1000005;
const double esp = 1e-5;
const ll mod = 1e9 + 7;

int n,k;
int ori[maxn];
int pos[maxn* 2];
int ans[maxn];
int que[maxn * 2];

int main()
{
	while(~scanf("%d%d",&n,&k))
	{
		for(int i = 1;i <= n;i++)scanf("%d",&ori[i]);
		int l = 1,r = 0;
		for(int i = 1;i <= k - 1;i++)
		{
			while(l <= r && que[r] >= ori[i])
				r--;
			que[++r] = ori[i];
			pos[r] = i;
		}
		for(int i = k;i <= n;i++)
		{
			while(l <= r && que[r] >= ori[i])
				r--;
			que[++r] = ori[i];
			pos[r] = i;
			while(pos[l] < i - k + 1)
				l++;
			printf(i == n ?"%d\n":"%d ",que[l]);
		}
		r = 0 , l = 1;
		for(int i = 1;i <= k - 1;i++)	
		{
			while(l <= r && que[r] <= ori[i])
				r--;
			que[++r] = ori[i];
			pos[r] = i;
		}
		for(int i = k;i <= n;i++)
		{
			while(l <= r && que[r] <= ori[i])
				r--;
			que[++r] = ori[i];
			pos[r] = i;
			while(pos[l] < i - k + 1)
				l++;
			printf(i == n ? "%d\n" : "%d ",que[l]);
		}
	}
	return 0;
}



单调队列最主要的用法还是用来优化dp,它能将类似dp[i] = max/min(f[k]) + g[i]的dp降一个维度。优化的对象就是f[k]    (k < i && g[i]是与k无关的量)


接下来来几个单调队列优化dp的例题:

http://acm.uestc.edu.cn/#/problem/show/594   我要长高

容易想到状态为dp[i][j],表示第i个人身高是j的最小费用

转移方程也比较容易想到,dp[i][j] = min(dp[i][j],dp[i - 1][k] + abs(j - k) * C + (j - ori[i]) ^ 2)

其中1<=k<=j,1<=j<=100,ori[i]为第i个人的原始身高

如果只是普通dp的话肯定会超时,因为要枚举每个人,枚举当前人的身高,枚举前一个人的身高,复杂度为O(n * h * h)

如果用单调队列优化,队列中已经存放了第i - 1的1~j-1的身高,所以枚举第i-1的k个身高就免了,复杂度降为O(n * h)

另外此题当前人只跟前面一个人的身高有关,所以可以用滚动数组

上面的状态转移方程要分j >= k和j < k考虑

j >= k时,dp[i][j] = min(dp[i][j],dp[i - 1][k] - k * C + j * C + (j - ori[i]) ^ 2)

j < k时,dp[i][j] = min(dp[i][j],dp[i - 1][k] + k * C - j * C  + (j - ori[i]) ^ 2)


#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,b,c) for(int a = b;a <= c;a++)
using namespace std;
typedef long long ll;
const int maxn = 50005;
const double esp = 1e-5;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;

int ori[maxn];
int que[maxn * 5];
int n,c;
int dp[maxn][102];


int main()
{
	while(~scanf("%d%d",&n,&c))
	{
		for(int i = 1;i <= n;i++)scanf("%d",ori + i);
		int now = 0,pre;
		for(int i = 1;i <= 100;i++)//初始化第一个人的费用
		{
			if(i < ori[1])dp[now][i] = INF;//变矮是不可能的,所以要设置无限大
				else dp[now][i] = (i - ori[1]) * (i - ori[1]);
		}
		int l,r;
		int val;
		for(int i = 2;i <= n;i++)
		{
			pre = now,now = 1 - now;//由于当前人的费用只由前一个人的身高决定,所以可以用滚动数组
			l = 0,r = 0;
			for(int j = 1;j <= 100;j++)//从1~100,枚举到j时,说明第i-1个人的前1~j-1的身高信息都在队列中了
			{
				val = dp[pre][j] - j * c;
				while(l < r && que[r - 1] >= val)
					r--;
				que[r++] = val;
				if(j < ori[i])//小于之前的身高是不可能的,下面同理
					dp[now][j] = INF;
				else dp[now][j] = que[l] + j * c + (j - ori[i]) * (j - ori[i]);
			}
			l = 0,r = 0;
			for(int j = 100;j >= 1;j--)
			{
				val = dp[pre][j] + j * c;
				while(l < r && que[r - 1] >= val)
					r--;
				que[r++] = val;
				if(j < ori[i])
					dp[now][j] = INF;
				else dp[now][j] = min(dp[now][j],que[l] - j * c + (j - ori[i]) * (j - ori[i]));
			}
		}
		int ans = INF;
		for(int i = 1;i <= 100;i++)
			ans = min(ans,dp[now][i]);
		cout<<ans<<endl;
	}
}





hdu3401:http://acm.hdu.edu.cn/showproblem.php?pid=3401

题意:某个人一开始有无限的钱,在第i天能以api的价格买如一个股票,以bpi的价格卖出一个股票,且当天最多能买asi个股票,最多能卖bsi个股票,这个人最多能持有maxp个股票。若一个人在第i天买入或者卖出股票,则最快只能在i + w + 1天再次买如或者卖出股票,问最多能能赚多少钱。


思路:状态转移方程容易列出  dp[i][j]表示第i天持有j个股票

dp[i][j] = max(dp[i - 1][j],dp[i][j])   第i-1天不买不卖到第i天

dp[i][j] = max(dp[i - 1 - w][k] - (j - k) * ap[i],dp[i][j]),第i-1-w天买如股票,可以化简为

dp[i][j] = max(dp[i - 1 - w][k] + k * ap[i] - j * ap[i],dp[i][j])

dp[i][j] = max(dp[i - 1 - w][k] - (k - j) * bp[i],dp[i][j]),第i-1-w天卖出股票,可以化简为

dp[i][j] = max(dp[i - 1 - w][k] - k * bp[i] + j * bp[i],dp[i][j])

由上面的式子可以看出可以用单调队列优化降维

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,b,c) for(int a = b;a <= c;a++)
using namespace std;
typedef long long ll;
const int maxn = 2005;
const double esp = 1e-5;
const ll mod = 1e9 + 7;
const int INF = 999999999;

int t;
int T,maxp,W;
struct ppp
{
    int ap,bp,as,bs;
    void read()
    {
        scanf("%d%d%d%d",&ap,&bp,&as,&bs);
    }
}d[maxn];

int que[maxn * 1000];
int pos[maxn * 1000];
int dp[maxn][maxn];

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&T,&maxp,&W);
        for(int i = 1;i <= T;i++)
            d[i].read();
        for(int i = 0;i <= T;i++)
            for(int j = 0;j <= maxp;j++)
                dp[i][j] = -INF;//先全部置为不可能的情况
        for(int i = 1;i <= W + 1;i++)
            for(int j= 0;j <= min(d[i].as,maxp);j++)
                dp[i][j] = - j * d[i].ap;//前w+1天可由第0天推出来,(其实第0天不存在)
        int now;
        for(int i = 1;i <= T;i++)
        {
            for(int j = 0;j <= maxp;j++)//当天的情况可由昨天推过来
                dp[i][j] = max(dp[i - 1][j],dp[i][j]);
            if(i <= W + 1)continue;
            int pre = i - W - 1;
            int l = 0,r = 0;
            for(int j = 0;j <= maxp;j++)
            {
                now = dp[pre][j] + j * d[i].ap;
                while(l < r && que[r - 1] < now)
                    r--;
                que[r] = now;pos[r++] = j;
                while(l < r && pos[l] + d[i].as < j)//队首元素持有的股票加上当天能买的股票比枚举的还要少,说明队首元素已经不可能推到当天
                    l++;
                dp[i][j] = max(dp[i][j],que[l] - j * d[i].ap);
            }
            l = 0,r = 0;
            for(int j = maxp;j >= 0;j--)
            {
                now = dp[pre][j] + j * d[i].bp;
                while(l < r && que[r - 1] < now)
                    r--;
                que[r] = now;pos[r++] = j;
                while(l < r && pos[l] - d[i].bs > j)
                    l++;
                dp[i][j] = max(dp[i][j],que[l] - j * d[i].bp);
            }
        }
        int ans = -INF;
        for(int i = 0;i <= maxp;i++)
            ans = max(ans,dp[T][i]);
        cout<<ans<<endl;
    }
}



poj1821:http://poj.org/problem?id=1821

题意:有k个工人,在si的方格上,他能连续地涂Li个格子(第si个方格必须包括),每涂一个能赚pi元,求涂方格赚取的最大值。


思路:状态方程dp[i][j],表示第i个工人涂到第j个方格赚取的最大利益,那么有状态转移方程

dp[i][j] = max(dp[i][j],dp[i - 1][j])到第j个i都不涂,由工人i-1涂到第j个来推出最大利益

dp[i][j] = max(dp[i][j - 1],dp[i][j])涂到第j个右涂到第j-1个推出最大利益

dp[i][j] = max(dp[i][j],dp[i - 1][k] + (j - k) * p[i])第i-1个工人只涂到第k个,第k + 1到第j个由i来涂,显然这个方程不用单调队列优化的是O(k * N * N),优化后可降维成O(k * N * N)


#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,b,c) for(int a = b;a <= c;a++)
using namespace std;
typedef long long ll;
const int maxn = 16010;
const double esp = 1e-5;
const int INF = 999999999;


int dp[105][maxn];
int n,k;
struct ppp
{
	int L,p,s,l,r;
	void read()
	{
		scanf("%d%d%d",&L,&p,&s);
	}
	void make()
	{
		l = max(1,s - L + 1);//计算左边界,当前油漆工向左所能涂到的最远的格子
		r = min(n,s + L - 1);//计算右边界,当前油漆工向右所能涂到的最远的格子
	}
	bool operator <(const ppp& b)const
	{
		return s < b.s;
	}
}p[105];
int que[maxn * 10];
int pos[maxn * 10];

int main()
{
	while(~scanf("%d%d",&n,&k))
	{
		for(int i = 1 ;i <= k;i++)p[i].read();
		sort(p + 1,p + 1 + k);
		for(int i=  1;i <= k;i++)p[i].make();
		for(int i = 0;i <= n;i++)dp[0][i] = 0;//初始化边界
		int l,r;
		int temp;
		for(int i = 1;i <= k;i++)
		{
			l = 0,r = 0;
			for(int j = 0;j <= n;j++)dp[i][j] = dp[i - 1][j];//就算当前油漆工什么都不干,也能右上一个油漆工得到当前值
			for(int j = p[i].l - 1;j < p[i].s;j++)//将i-1个油漆工涂到k的情况加入单调队列
			{
				temp = dp[i - 1][j] - j * p[i].p;
				while(l < r && que[r - 1] <= temp)
					r--;
				que[r] = temp;pos[r] = j;r++;
			}
			for(int j = p[i].s;j <= p[i].r;j++)//取出最优情况
			{
				while(l < r && j - pos[l] > p[i].L)
					l++;
				dp[i][j] = max(dp[i][j],dp[i][j - 1]);//涂到第j的最大值可以由到j-1涂,第j个不涂推出
				dp[i][j] = max(dp[i][j],que[l] + j * p[i].p);
			}
			for(int j = p[i].r + 1;	j <= n;j++)dp[i][j] = max(dp[i][j - 1],dp[i - 1][j]);//后面一些不可涂到的地方可由上一个工人的涂到j的最大值和当前工人涂到j-1的最大值推出
		}
		int ans = 0;
		for(int i = 1;i <= n;i++)
			ans = max(ans,dp[k][i]);
		printf("%d\n",ans);
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值