单调队列优化dp

首先你得知道单调队列是个啥!!!!!!!!!!!!!!

简单来说

就是一个单调的队列(像是在放屁)

记住,千万不要用queue,难死你,就用一个数组,一个头指针一个尾指针

从尾指针添加元素(当然也可以删除)从前面提取最优值

用术语来说就是维护一个单调队列;(明白了吧???)

自己去网上搜吧

 

接下来进入正题了::::用这个鬼东西来优化dp

学了这么长时间dp了

你也知道dp的涉及面有多么广了,绝对不会让你只想出一个转移方程就AC;

这时候就要用到各种优化了;

 

先拿一个题;

 

F. 最大子序和

内存限制:128 MiB 时间限制:1000ms   题目类型:传统 评测方式:文本比较

 

 

 

题目描述

输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。

例如 1,-3,5,1,-2,3

当m=4时,S=5+1-2+3=7

当m=2或m=3时,S=5+1=6

 

输入格式

第一行两个数n,m

第二行有n个数,要求在n个数找到最大子序和

 

输出格式

一个数,数出他们的最大子序和

 

样例

样例输入

6 4
1 -3 5 1 -2 3

样例输出

7

 

 

作为一个老dp人了

你不会连转移方程都想不出来吧;

我劝你重修dp吧

既然是求子序和,还是连续的!?(那必是前缀和走起啊)

设num[i]为这个序列,sum[i]为前i个的和,dp[i]为以a[i]为最后一个数的最大子序列;

好吧我承认我之前说错了,这个题的转移方程好像并不存在。。。。。

(姑且称它为一个dp题吧)

dp[i]=sum[i]-sum[j](i>j && i-j<=m)

那么我们就去维护一个单调队列,在sum[i]这个数组的基础上,使这个单调队列递增;

然后,贴上代码

 

#include<bits/stdc++.h>
using namespace std;
int n,m,num[300005];
int sum[300005];
int dp[300005];
int q[300005];
int ans=0x8fffffff;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
        sum[i]=sum[i-1]+num[i];
    }
    int l=1,r=0;
    q[1]=1;
    for(int i=1;i<=n;i++){
        dp[i]=sum[i]-sum[i-1];
        while(l<=r && q[l]<i-m) l++;
        if(l<=r) dp[i]=sum[i]-sum[q[l]];
        ans=max(ans,dp[i]);
        while(l<=r && sum[i]<sum[q[r]]) r--;
        q[++r]=i;
    }
    printf("%d",ans);
}

 

那么,从现在开始

恭喜你正式踏入单调队列优化dp的大门;

好了接着讲下一道题目;;;;

B. 股票交易

内存限制:512 MiB 时间限制:1000 ms 标准输入输出  题目类型:传统 评测方式:文本比较
 

题目描述

最近lxhgww又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。 通过一段时间的观察,lxhgww预测到了未来T天内某只股票的走势,第i天的股票买入价为每股APi,第i天的股票卖出价为每股BPi(数据保证对于每个i,都有APi>=BPi),但是每天不能无限制地交易,于是股票交易所规定第i天的一次买入至多只能购买ASi股,一次卖出至多只能卖出BSi股。 另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔W天,也就是说如果在第i天发生了交易,那么从第i+1天到第i+W天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过MaxP。 在第1天之前,lxhgww手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,T天以后,lxhgww想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?

输入格式

输入数据第一行包括3个整数,分别是T,MaxP,W。 接下来T行,第i行代表第i-1天的股票走势,每行4个整数,分别表示APi,BPi,ASi,BSi。

输出格式

输出数据为一行,包括1个数字,表示lxhgww能赚到的最多的钱数。

样例

样例输入

5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1

样例输出

3

数据范围与提示

对于30%的数据,0<=W<T<=50,1<=MaxP<=50

对于50%的数据,0<=W<T<=2000,1<=MaxP<=50

对于100%的数据,0<=W<T<=2000,1<=MaxP<=2000

对于所有的数据,1<=BPi<=APi<=1000,1<=ASi,BSi<=MaxP

这个题就稍微复杂那么一点点;
做优化dp的题时
无论是什么题
都要先把最普通的转移方程写出来,然后再去优化
这是基础不能跳过
啊我太懒了
看代码吧
#include<bits/stdc++.h>
using namespace std;
int t,maxp,w;
int ap[2005],bp[2005],as[2005],bs[2005];
int dp[2005][2005];
int main()
{
    memset(dp,0x8f,sizeof(dp));
    scanf("%d%d%d",&t,&maxp,&w);
    for(int i=1;i<=t;i++){
        scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
    }
    for(int i=1;i<=t;i++){
        for(int j=0;j<=as[i];j++) dp[i][j]=-j*ap[i];
        for(int j=0;j<=maxp;j++) dp[i][j]=max(dp[i][j],dp[i-1][j]);
        if(i<=w+1) continue;
        int l=1,r=0;
        int q[2005]={};
        int u=i-w-1;
        for(int j=0;j<=maxp;j++){
            while(l<=r && dp[u][j]+j*ap[i]>=dp[u][q[r]]+q[r]*ap[i]) r--;
            while(l<=r && q[l]<j-as[i]) l++;
            q[++r]=j;
            dp[i][j]=max(dp[u][q[l]]+q[l]*ap[i]-j*ap[i],dp[i][j]);
        }
        l=1;r=0;
        for(int j=maxp;j>=0;j--){
            while(l<=r && dp[u][j]+j*bp[i]>=dp[u][q[r]]+q[r]*bp[i]) r--;
            while(l<=r && q[l]>j+bs[i]) l++;
            q[++r]=j;
            dp[i][j]=max(dp[u][q[l]]+q[l]*bp[i]-j*bp[i],dp[i][j]);
        }
    }
    int ans=0x8fffffff;
    for(int i=0;i<=maxp;i++){
        ans=max(ans,dp[t][i]);
        //printf("%d\n",dp[t][i]);
    }
    printf("%d",ans);
}

剩下的你们自己搞吧

还有个比较重要的东西

记住你找完转移方程之后

化简一下

比如股票交易的方程(i天数,j现在拥有的股票数)

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

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

只看这两个方程自然是啥也看不出来

你要找到不变量

k是你将要买或者卖出去的数量

这样想   k=maxp-ki;

那么我们完全可以先加或者减去maxp*ap[i](bp[i]);

这样就变成了dp[i][j]=max(dp[i-w-1]+maxp*ap[i]-ki*ap[i])    卖出去同理

再将ki加回来(减)

注意卖股票的循环是倒序的

就像背包一样

防止紊乱

单调队列优化的dp基本都是这样

先加上总的,然后再一点点减,这样就实现了单调性

恩的~~~~~~

 

posted @ 2021-02-02 21:36  fengwu2005  阅读(90)  评论(1编辑  收藏  举报