单调队列 例子:hdu3401 trade(dp加单调队列)

8 篇文章 1 订阅
2 篇文章 0 订阅

单调队列指的是指队列中的元素是单调的。如:{a1,a2,a3,a4……an}满足a1<=a2<=a3……<=an,a序列便是单调递增序列。同理递减队列也是存在的。单调队列出现的机会不多,而且最常和dp一起出现。单调队列是一种工具而不是解题的方法。
单调队列的出现可以简化问题,队首元素便是最大(小)值,这样,选取最大(小)值的复杂度便为o(1),由于队列的性质,每个元素入队一次,出队一次,维护队列的复杂度均摊下来便是o(1)。

如何维护单调队列呢,以单调递增序列为例,我们需要维护队首和队尾指针:
1、如果队列的长度一定,先判断队首元素是否在规定范围内,如果超范围则增长队首。
2、每次加入元素时和队尾比较,如果当前元素小于队尾且队列非空,则减小尾指针,队尾元素依次出队,直到满足队列的单调性为止。

hdu 3401 trade
Problem Description
Recently, lxhgww is addicted to stock, he finds some regular patterns after a few days’ study.
He forecasts the next T days’ stock market. On the i’th day, you can buy one stock with the price APi or sell one stock to get BPi.
There are some other limits, one can buy at most ASi stocks on the i’th day and at most sell BSi stocks.
Two trading days should have a interval of more than W days. That is to say, suppose you traded (any buy or sell stocks is regarded as a trade)on the i’th day, the next trading day must be on the (i+W+1)th day or later.
What’s more, one can own no more than MaxP stocks at any time.

Before the first day, lxhgww already has infinitely money but no stocks, of course he wants to earn as much money as possible from the stock market. So the question comes, how much at most can he earn?

Input
The first line is an integer t, the case number.
The first line of each case are three integers T , MaxP , W .
(0 <= W < T <= 2000, 1 <= MaxP <= 2000) .
The next T lines each has four integers APi,BPi,ASi,BSi( 1<=BPi<=APi<=1000,1<=ASi,BSi<=MaxP), which are mentioned above.

Output
The most money lxhgww can earn.

Sample Input
1
5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1

Sample Output
3
思路:
dp[i][j]:表示第i天手上持有j股的最大利益。分情况:

dp[i][j]=dp[i-1][j] 不交易
dp[i][j]=max( dp[r][k]-ap[i]*(j-k) | j>=k,r<=i-w-1) 买进
dp[i][j]=max(dp[r][k]+bp[i]*(k-j) | j<=k,r<=i-w-1) 卖出
由于dp[i][j]不会小于之前的 dp[k][j],k < i ,所以r=i-w-1
这里有个可以使用单调队列的特征
dp[i][j]=(dp[r][k]+ap[i] * k)- ap[i] * j;
求dp[i][j]时只需求之前最大的dp[r][k]+ ap[i] * k,所以可以用单调队列维护。
卖出时同理。
由于自己写的代码挺乱的,所以拉来了一篇网上写的比较清晰的代码,也可以作为单调队列的模板来使用

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

const int maxn=2005;
const int oo=0x3fffffff;
int ap[maxn], bp[maxn], as[maxn], bs[maxn];
int dp[maxn][maxn];
int W, T, Maxp, n;

struct node
{
    int num, val;
}que[maxn];

int main()
{
    cin >> T;
    while(T--)
    {
        cin >> n >> Maxp >> W;
        for(int i=0; i<=n; i++)
            for(int j=0; j<=Maxp; j++) dp[i][j]=-oo;
        for(int i=1; i<=n; i++) scanf("%d%d%d%d",ap+i,bp+i,as+i,bs+i);
        for(int i=1; i<=n; i++) dp[i][0]=0;
        for(int i=1; i<=W+1; i++)
            for(int j=1; j<=as[i]; j++) dp[i][j]=-j*ap[i];
        for(int j=1; j<=Maxp; j++)
             for(int i=2; i<=W+1; i++)
               dp[i][j]=max(dp[i][j],dp[i-1][j]);
        for(int i=W+2; i<=n; i++)
        {
            int front=0, tail=-1;
            for(int j=0; j<=Maxp; j++)
            {
                dp[i][j]=max(dp[i][j],dp[i-1][j]);
                //买进时单调队列的一.二步
                while(front<=tail&&que[tail].val<=dp[i-W-1][j]+ap[i]*j) tail--;
                que[++tail].val=dp[i-W-1][j]+ap[i]*j, que[tail].num=j;
                while(front<=tail&&j-que[front].num>as[i]) front++;
                dp[i][j]=max(dp[i][j],que[front].val-ap[i]*j);
            }
            front=0, tail=-1;
            for(int j=Maxp; j>=0; j--)
            {
                while(front<=tail&&que[tail].val<=dp[i-W-1][j]+bp[i]*j) tail--;
                que[++tail].val=dp[i-W-1][j]+bp[i]*j, que[tail].num=j;
                while(front<=tail&&que[front].num-j>bs[i]) front++;
                dp[i][j]=max(dp[i][j],que[front].val-bp[i]*j);
            }
        }
        int maxx=0;
        for(int i=0; i<=Maxp; i++)
              maxx=max(maxx,dp[n][i]);
        printf("%d\n",maxx);
    }
    return 0;
}

遇到怎样的题使用单调队列:
做动态规划时常常会见到形如这样的转移方程:

  f[x] = max or min{g(k) | b[x] <= k < x} + w[x]

  (其中b[x]随x单调不降,即b[1]<=b[2]<=b[3]<=…<=b[n])

  (g[k]表示一个和k或f[k]有关的函数,w[x]表示一个和x有关的函数)
  例如上一题就是满足这样的方程,所以可以使用单调队列优化。
  这个方程怎样求解呢?我们注意到这样一个性质:如果存在两个数j, k,使得j <= k,而且g(k) <= g(j),则决策j是毫无用处的。因为根据b[x]单调的特性,如果j可以作为合法决策,那么k一定可以作为合法决策,又因为k比j要优,(注意:在这个经典模型中,“优”是绝对的,是与当前正在计算的状态无关的),所以说,如果把待决策表中的决策按照k排序的话,则g(k)必然是不降的。

  这样,就引导我们使用一个单调队列来维护决策表。对于每一个状态f(x)来说,计算过程分为以下几步:

  1、 队首元素出队,直到队首元素在给定的范围中。

  2、 此时,队首元素就是状态f(x)的最优决策,

  3、计算g(x),并将其插入到单调队列的尾部,同时维持队列的单调性(不断地出队,直到队列单调为止)。

  重复上述步骤直到所有的函数值均被计算出来。不难看出这样的算法均摊时间复杂度是O(1)的。因此求解f(x)的时间复杂度从O(n^2)降到了O(n)。

单调队列指一个队列中的所有的数符合单调性(单调增或单调减),在信息学竞赛的一些题目上应用,会减少时间复杂度

单调队列的每个元素一般会存储两个值:

**1.在原数列中的位置(下标)
2.该元素在动态规划中的状态值(价值)**
上题中的结构体
单调队列同时保证这两个值单调。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于HDU4546问题,还可以使用优先队列(Priority Queue)来解决。以下是使用优先队列的解法思路: 1. 首先,将数组a进行排序,以便后续处理。 2. 创建一个优先队列(最小堆),用于存储组合之和的候选值。 3. 初始化优先队列,将初始情况(即前0个数的组合之和)入队列。 4. 开始从1到n遍历数组a的元素,对于每个元素a[i],将当前队列中的所有候选值取出,分别上a[i],然后再将和的结果作为新的候选值入队列。 5. 重复步骤4直到遍历完所有元素。 6. 当队列的大小超过k时,将队列中的最小值弹出。 7. 最后,队列中的所有候选值之和即为前k小的组合之和。 以下是使用优先队列解决HDU4546问题的代码示例: ```cpp #include <iostream> #include <vector> #include <queue> #include <functional> using namespace std; int main() { int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a.begin(), a.end()); // 对数组a进行排序 priority_queue<long long, vector<long long>, greater<long long>> pq; // 最小堆 pq.push(0); // 初始情况,前0个数的组合之和为0 for (int i = 0; i < n; i++) { long long num = pq.top(); // 取出当前队列中的最小值 pq.pop(); for (int j = i + 1; j <= n; j++) { pq.push(num + a[i]); // 将所有和结果作为新的候选值入队列 num += a[i]; } if (pq.size() > k) { pq.pop(); // 当队列大小超过k时,弹出最小值 } } long long sum = 0; while (!pq.empty()) { sum += pq.top(); // 求队列中所有候选值之和 pq.pop(); } cout << sum << endl; return 0; } ``` 使用优先队列的方法可以有效地找到前k小的组合之和,时间复杂度为O(nklog(k))。希望这个解法对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值