单调队列优化dp [HDU2191][HDU3401][POJ1821]

用单调队列优化形如 dp[i][j] = max(dp[i - 1][k] + (j - k) * a[i]) 的状态转移

上式可化为dp[i][j] = max(dp[i - 1][k] - k * a[i]) + j * a[i],

在不用单调队列优化前,对于dp[i][j] 和dp[i][j + 1]枚举 k 时发现,有重复的部分,而且都是取dp[i - 1][...k...]...的最大值或最小值,

用优先队列装着dp[i - 1][k] - k*a[i],在 i 确定,枚举 j 的过程中,每枚举到一个j都会有对应的dp[i - 1][j] - j*a[i] 加入优先队列,

这样对于不同的 j 我们就不用每次从头枚举k了,每次只需从队首取出元素

注意下面几点:

1)取队首元素时,在队首的(k的范围)不符合条件的元素出队,直到取到符合条件的元素,用于更新dp[ i ][ j ]

2)加入元素时(以队首最大的队列为例),从尾部开始,小于当前入队元素的队中元素删掉,将入队元素放到队尾

3)每个元素入队一次,出队一次,相当于在第二层(枚举j)时几乎没增加复杂度,这样我们就将3层循环化为了2层


题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191

题意:多重背包

思路:数据范围不大,可以用一般多重背包的方法先二进制优化,转成01背包做。但在这里学习一下单调队列优化的方法。

本来的状态转移方程是dp[j] = max(dp[j - wi] +vi], dp[j]) 

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int w[105], v[105], n[105];
int dp[105], Q[105], P[105];

int main()
{
    #ifdef LOCAL
    freopen("dpdata.txt", "r", stdin);
    #endif

    int cas, N, W;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d%d", &W, &N);
        for(int i = 1; i <= N; i++)
        {
            scanf("%d%d%d", &w[i], &v[i], &n[i]);
        }
        memset(dp, 0, sizeof(dp));
        for(int i = 1; i <= N; i++)
        {
            for(int a = 0; a < w[i]; a++)
            {
                int s = 0, t = 0;
                Q[0] = 0; P[0] = 0;
                for(int j = 0; j * w[i] + a <= W; j++)
                {
                    int temp = dp[j * w[i] + a] - j * v[i];
                    while(s <= t && Q[t] <= temp) t--;
                    Q[++t] = temp;
                    P[t] = j;
                    while(s <= t && P[s] < j - n[i]) s++;
                    dp[j * w[i] + a] = Q[s] + j * v[i];
                }
            }
        }
        printf("%d\n", dp[W]);
    }

    return 0;
}


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

题意:买卖同一个股票,给出t天,每天可以用ap的价格买一份,bp的价格卖一份,每天最多买as份,卖bs份,问t天后最多能赚多少钱。两次交易需间隔w天。股票的价值不用考虑。

思路:

dp[ i ][ j ] 记录第 i 天后持有 j 份股票时最多赚了多少钱 

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

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

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


#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 50000000
using namespace std;
typedef long long ll;
int ap[2005], bp[2005], as[2005], bs[2005];
int dp[2005][2005], Q[2005], P[2005];//Q记录值,P记录下标k

int main()
{
    #ifdef LOCAL
    freopen("dpdata.txt", "r", stdin);
    #endif

    int cas, n, maxp, w;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d%d%d", &n, &maxp, &w);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]);
        }
        for(int i = 0; i <= n; i++)
        {
            for(int j = 0; j <= maxp; j++)
                dp[i][j] = -INF;
        }
        dp[0][0] = 0;
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= min(maxp, as[i]); j++)
                dp[i][j] = - j * ap[i];
        }
        for(int i = 1; i <= n; i++)
        {
                /*本来的状态转移,3个循环,在此基础上更改
                for(int j = 0; j <= maxp; j++)
                for(int k = max(0, j - as[i]); k <= j; k++)
                {
                    dp[i][j] = max(dp[i - w - 1][k] - (j - k) * ap[i], dp[i][j]);
                }
                for(int k = j; k <= min(j + bs[i], maxp); k++)
                {
                    dp[i][j] = max(dp[i - w - 1][k] + (k - j) * bp[i], dp[i][j]);
                }*/
            for(int j = 0; j <= maxp; j++)//不买不卖
                dp[i][j] = max(dp[i][j], dp[i - 1][j]);

            if(i - w - 1 <= 0) continue;
            int s = 0, t = -1;
            for(int j = 0; j <= maxp; j++)
            { //买
                int temp = dp[i - w - 1][j] + j * ap[i];//从队尾找位置加入元素,同时删除多余元素
                while(s <= t && Q[t] <= temp) t--;
                Q[++t] = temp; P[t] = j;
                while(s <= t && j - P[s] > as[i]) s++;//删除k范围不符合的元素
                dp[i][j] = max(Q[s] - j * ap[i] ,dp[i][j]);
            }
            s = 0; t = -1;
            for(int j = maxp; j >= 0; j--)
            { //卖
                int temp = dp[i - w - 1][j] + j * bp[i];
                while(s <= t && Q[t] <= temp) t--;
                Q[++t] = temp; P[t] = j;
                while(s <= t && P[s] - j > bs[i]) s++;
                dp[i][j] = max(Q[s] - j * bp[i], dp[i][j]);
            }

            //printf("dp[%d][%d]=%d\n", i, j, dp[i][j]);
        }
        int ans = 0;
        for(int i = 0; i <= maxp; i++)
            ans = max(ans, dp[n][i]);
        printf("%d\n", ans);
    }

    return 0;
}


题目:http://poj.org/problem?id=1821

题意:栅栏长n个单位,有k个工人,每个工人可刷连续长度为Li,没刷一个长度赚钱Pi,他们站在固定的位置Si(不重复),问这k个工人最多一共赚多少钱。每个工人如果刷的话,必刷了自己所在的位置。

思路:

先将工人按所在位置排序,dp[ i ][ j ] 表示第i个工人刷完,刷到前 j 个板最多赚多少钱。

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int dp[110][16010];
int Q[16010], P[16010];//Q记录值,P记录下标k

struct Worker
{
    int l, p, s;
}wk[105];
int cmp(Worker a, Worker b)
{
    return a.s < b.s;
}

int main()
{
    #ifdef LOCAL
    freopen("dpdata.txt", "r", stdin);
    #endif

    int m, n;
    while(~scanf("%d%d", &m, &n))
    {
        memset(wk, 0, sizeof(wk));
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%d", &wk[i].l, &wk[i].p, &wk[i].s);
        }
        sort(wk + 1, wk + n + 1, cmp);
        memset(dp, 0, sizeof(dp));
        for(int i = 1; i <= n; i++)
        {
            dp[i][0] = 0;
            int s = 0, t = 0;
            Q[0] = 0; P[0] = 0;
            for(int j = 1; j <= m; j++)
            {
                /*原状态转移
                for(int k = j - wk[i].l; k <= j; k++)
                {
                    dp[i][j] = max(dp[i - 1][k] + (j - k) * wk[i].p, dp[i][j]);
                }*/
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);//第i个工人不刷j或第j格没被刷
                if(wk[i].s - wk[i].l <= j && j < wk[i].s)
                {//j为工人i刷时可能的开始位置
                    int temp = dp[i - 1][j] - j * wk[i].p;
                    while(s <= t && Q[t] <= temp) t--;
                    Q[++t] = temp;
                    P[t] = j;
                }
                if(wk[i].s <= j && j < wk[i].s + wk[i].l)
                { //j为工人i刷时可能的结束位置
                    while(s <= t && P[s] + wk[i].l < j) s++;
                    dp[i][j] = max(Q[s] + j * wk[i].p, dp[i][j]);
                }
            }
        }
        printf("%d\n", dp[n][m]);
    }

    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值