单调队列优化DP

时间:2021-02-24 17:38:24

做了几道前几天多校的单调队列优化DP题目:

hdu 4326 Dragon Ball

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

题意:

Sean的到一个地图上边标有什么时刻什么地点会出现龙珠,龙珠在每一时刻出现在同一行里,Sean每一时刻只能去一个龙珠,给出他取龙珠所消耗的能量以及移动所消耗的能量。求他在每个时间段取龙珠的最小能量消耗;

思路:

dp[i][j]表示在i时间取第j个龙珠的最小能量消耗

则有dp[i][j] = min(dp[i - 1][k],abs(p[i - 1][k].pos - p[i][j].pos)) + p[i][j].w;

此方法复杂度为O(m*n^2)会超时

 假设如果都是从当前点的左边来的则有:

dp[i][j] = min(dp[i - 1][k] - p[i - 1][k].pos) + p[i][j].pos + p[i][j].w;

而对于dp[i - 1][k] - p[i - 1][k].pos为定值可以用单调队列维护,从右边来的同理;

这里我们主要是是在在对左边排序上的复杂度优化为O(m*nlogn)

话说奇了个怪了,昨天晚上写了很长时间就是不对,今天早上写完就A了。。无语了。。

单调队列优化DP单调队列优化DPView Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define N 1007
#define M 55
using namespace std;

struct node
{
    int pos;
    int w;
}p[M][N];
int dp[M][N],q[N];
int n,m,pos;
int cmp(node a,node b)
{
    return a.pos < b.pos;
}
int main()
{
    //freopen("din.txt","r",stdin);
    int t,i,j,k;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d%d",&m,&n,&pos);
        //cout<<m << n << pos << endl;
        for (i = 1; i <= m; ++i)
        {
            for (j = 1; j <= n; ++j)
            scanf("%d",&p[i][j].pos);
        }
        for (i = 1; i <= m; ++i)
        {
            for (j = 1; j <= n; ++j)
            scanf("%d",&p[i][j].w);

            sort(p[i] + 1,p[i] + 1 + n,cmp);//对坐标排序
        }

        for (i = 1; i <= n; ++i)
        dp[1][i] = p[1][i].w + abs(pos - p[1][i].pos);//出事化第一行

        int front,tail;
        for (i = 2; i <= m; ++i)
        {
            front = 0; tail = -1;
            for (j = k = 1; j <= n; ++j)
            {
                dp[i][j] = inf;
                while (k <= n && p[i - 1][k].pos <= p[i][j].pos)//单调队列维护j左边取余最小值
                {
                    int tmp = dp[i - 1][k] - p[i - 1][k].pos;
                    while (tail >= front && dp[i - 1][q[tail]] - p[i - 1][q[tail]].pos >= tmp) tail--;
                    q[++tail] = k; ++k;
                }
                if (tail >= front) dp[i][j] = dp[i - 1][q[front]] + p[i][j].pos - p[i - 1][q[front]].pos + p[i][j].w;
            }
            front = 0; tail = -1;
            for (j = k = n; j >= 1; --j)
            {
                while (k >= 1 && p[i - 1][k].pos >= p[i][j].pos)//维护j右边的区间取最大值
                {
                    int tmp = dp[i - 1][k] + p[i - 1][k].pos;
                    while (tail >= front && dp[i - 1][q[tail]] + p[i - 1][q[tail]].pos >= tmp) tail--;
                    q[++tail] = k; --k;
                }
                if (tail >= front) dp[i][j] = min(dp[i][j],dp[i - 1][q[front]] + p[i - 1][q[front]].pos - p[i][j].pos + p[i][j].w);
            }
        }
        int MIN = inf;
        for (i = 1; i <= n; ++i)
        {
            //printf("%d\n",dp[m][i]);
            MIN = min(MIN,dp[m][i]);
        }
        printf("%d\n",MIN);
    }
    return 0;
}

 

hdu 4374 One hundred layer

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

和上一道题目类似,不过这里要对单调队列的长度进行一下维护。

题意:

有一个n曾的楼,从第一层往上走,每一层都有m块,可以从对应的本层的第y块跳到上一层的第y块,要求限制每一层只能往左右走,而且走的最大长度为T,问到达第n才层后的最大得分(ps:这里每一块都对应着一个得分,给出n*m矩阵表示得分);

思路:

dp[i][j] = max(dp[i - 1][k] + abs(dis(k,j)))  j - T <= k <= j + T;

如果暴力的话时间复杂度为O(n*m^2)超时,这里我们可以从j的左边枚举一下右边枚举一下单调队列维护1-j或者j - n区间的最值,进行dp即可。

单调队列优化DP单调队列优化DPView Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 10007
#define N 107
using namespace std;


int sum[N][M];
int dp[N][M],q[M];
int n,m,T,pos;

int main()
{
   //freopen("din.txt","r",stdin);
    int i,j;
    while (~scanf("%d%d%d%d",&n,&m,&pos,&T))
    {
        CL(sum,0);
        for (i = 1; i <= n; ++i)
        {
            for (j = 1; j <= m; ++j)
            {
                scanf("%d",&sum[i][j]);
                sum[i][j] += sum[i][j - 1];//累加求个区间和
                dp[i][j] = -inf;
            }
        }
        /*for (i = 1; i <= n; ++i)
        {
            for (j = 1; j <= m; ++j)
            printf("%d ",sum[i][j]);
            puts("");
        }*/
        //初始化第一行
        for (i = pos; i <= m && i <= pos + T; ++i)
        dp[1][i] = sum[1][i] - sum[1][pos - 1];
        for (i = pos - 1; i >= 1 && i >= pos - T; --i)
        dp[1][i] = sum[1][pos] - sum[1][i - 1];
        /*for (i = 1; i <= m; ++i) printf("%d ",dp[1][i]);
        puts("");*/
        int front,tail;
        for (i = 2; i <= n; ++i)
        {
            //左扫
            front = 0,tail = -1;
            for (j = 1; j <= m; ++j)
            {
                while (tail >= front && j - q[front] > T) front++;//维护单调队列的长度
                if (dp[i - 1][j] != -inf)//因为这里上一层可能存在不能到达的点也就不能更新下一层
                {
                    int tmp = dp[i - 1][j] + sum[i][j] - sum[i][j - 1];
                    while (tail >= front && dp[i - 1][q[tail]] + sum[i][j] - sum[i][q[tail] - 1] < tmp) tail--;//单调队列里维护的是到j - 1分值最高的的对于j来说也是1到j-1点里面到达他的分值最高的的,自己yy吧
                    q[++tail] = j;
                }
                if (tail >= front) dp[i][j] = dp[i - 1][q[front]] + sum[i][j] - sum[i][q[front] - 1];
            }
            //右扫
            front = 0,tail = -1;
            for (j = m; j >= 1; --j)
            {
                while (tail >= front && q[front] - j > T) front++;
                if (dp[i - 1][j] != -inf)
                {
                    int tmp = dp[i - 1][j] + sum[i][j] - sum[i][j - 1];
                    while (tail >= front && dp[i - 1][q[tail]] + sum[i][q[tail]] - sum[i][j - 1] < tmp) tail--;
                    q[++tail] = j;
                }
                if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - 1][q[front]] +  sum[i][q[front]] - sum[i][j - 1]);
            }
        }
        int MAX = -inf;
        for (i = 1; i <= m; ++i)
        {
           // cout<<">>"<<dp[n][i]<<endl;
            MAX = max(MAX,dp[n][i]);
        }
        printf("%d\n",MAX);
    }
    return 0;
}

 

 hdu 3401 Trade

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

题意:

给出T天的买卖股票状况,在第i天买一股的费用为ap[i],卖一股的费用为bp[i],而且两次交易(买或者卖)的时间差必须大于w天,规定一天最多买卖maxp股股票。给出所有数据求解最后lxhgww 能赚得的最多的钱。

思路:

动态规划,每一天都有三种情况,不买不卖,买或者卖

状态转移方程是:

对于买

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

对于卖

dp[i][j] = max (dp[i][j], dp[r][k] + (j - k) * ap[i]) 1 <= r <= i - w -1 , min(maxp, j + bs[i]) >k > j

 总的时间复杂度是0(maxp*maxp*T*T),一定要超时,要降低复杂度

 

首先bp[i]>=ap[i]所以对于同一天的商品只能买如果卖的话指定会赔本或者不变。前w天的的商品之间不会进行转移只会不买不卖的传递。

首先对于每一天,枚举下j做一次

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

这样就可以保证对于dp[i][j],dp[i-w-1][]已经包含了前面的最好情况

不用再枚举上次交易是哪一天 1 <= k <= i - w -1这一维了

时间复杂度便成为0(maxp*maxp*T),不过还是不行

然后转移的时候:(只讨论买的情况)

dp[i-1][r]-(j-r)*ap[i]

即 dp[i-1][r]-j*ap[i]+r*ap[i]

外层循环 i,j 必要

则i j确定 ==> j*ap[i] 确定

最优的是dp[i-1][r]+r*ap[i]最优(只与上层有关)

每次i,j循环都要计算但是我们可以把每个r对应的值算出来维护个单调队列

卖一样;

单调队列优化DP单调队列优化DPView Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 10007
#define N 2007
using namespace std;

int dp[N][N];
int ap[N],bp[N],as[N],bs[N];
int maxp,T,w;
int q[N];

int main()
{
    //freopen("din.txt","r",stdin);
    int t,i,j;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d%d",&T,&maxp,&w);
        for (i = 1; i <= T; ++i)
        scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);

        for (i = 1; i <= T; ++i)
        for (j = 0; j <= maxp; ++j) dp[i][j] = -inf;//初始化

        for (i = 1; i <= w + 1; ++i)//前w天买
        {
            for (j = 0; j <= min(maxp,as[i]); ++j)
            {
                dp[i][j] = -ap[i]*j;
            }
        }
        int front,tail;
        for (i = 2; i <= T; ++i)
        {
            for (j = 0; j <= maxp; ++j)//保证dp[i - w - 1][j]是最优的
            {
                dp[i][j] = max(dp[i][j],dp[i - 1][j]);
            }
            if (i < w + 2) continue;
            //
            front = 0; tail = -1;
            for (j = 0; j <= maxp; ++j)
            {
                while (tail >= front && q[front] + as[i] < j) front++;//单调队列维护长度不能超过as
                int tmp = dp[i - w - 1][j];
                while (tail >= front && dp[i - w - 1][q[tail]] - ap[i]*(j - q[tail]) < tmp) tail--;
                q[++tail] = j;
                if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - w - 1][q[front]] - ap[i]*(j - q[front]));
            }
            //
            front = 0; tail = -1;
            for (j = maxp; j >= 0; --j)
            {
                while (tail >= front && q[front] - bs[i] > j) front++;
                int tmp = dp[i - w - 1][j];
                while (tail >= front && dp[i - w - 1][q[tail]] + bp[i]*(q[tail] - j) < tmp) tail--;
                q[++tail] = j;
                if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - w - 1][q[front]] + bp[i]*(q[front] - j));
            }
        }
        int MAX = -inf;
        for (i = 0; i <= maxp; ++i)
        {
            //cout<<dp[T][i]<<endl;
            MAX = max(MAX,dp[T][i]);
        }
        printf("%d\n",MAX);
    }
    return 0;
}

 

hdu 3905  Sleeping

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

题意:

ZZZ上一堂课有N分钟,给出每分钟他听课的话能够得到的学分,并且规定在这N分钟里他必须休息至少m分钟,而且如果他听课必须听连续的长度为L分钟的课,问它能够获得的最多的学分。

思路:
dp[i][j] = max(dp[i -1][j - 1],dp[k][j] + sum[i] - sum[k]) dp[i][j]表示前i分钟休息了j分钟所获得的最大得分。如果正常递推的话是O(n^3)肯定会超时,由于k属于[j,i - L]一个区间,只要维护区间最值即可
 ,于是我想到了优先队列,可是如果for(i:n) for(j:m) 推得话,dp[k][j] - sum[k]这里在单调队列里面的dp[k][j]就会变化,而不是我们想要的了,于是就纠结啊纠结。其实这里只要将循环互换,for(j:m)for (i:m)这样每一层之和上一层有关了,而且每一层的j都是相同的,这样就保证了我们单调队列里面的值不会改变。哎,这样的优化题目还是不熟啊。

单调队列优化DP单调队列优化DPView Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a , b) ((a) < (b) ? (a) : (b))
#define Max(a , b) ((a) > (b) ? (a) : (b))

#define ll __int64
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 1007
#define N 1007
using namespace std;
//freopen("din.txt","r",stdin);

int dp[N][M];
int q[M],sum[N];

int Get(){
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    int num = 0;
    while (ch >= '0' && ch <= '9'){
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;
}
int main(){
    int i,j;
    int n,m,L;
    while (~scanf("%d%d%d",&n,&m,&L)){
        sum[0] = 0;
        for (i = 1; i <= n; ++i){
            sum[i] = Get();
            sum[i] += sum[i - 1];
        }
        CL(dp,0);
        for (i = L; i <= n; ++i) dp[i][0] = sum[i];

        int fr,tl;
        int k;
        
        //循环倒置关键
        for (j = 1; j <= m; ++j){
            k = j; fr = 0; tl = 0;
            for (i = L; i <= n; ++i){
                dp[i][j] = dp[i - 1][j - 1];
                while (k >= j && k <= i - L){
                    while (fr <= tl && dp[q[tl]][j] - sum[q[tl]] <= dp[k][j] - sum[k]) tl--;
                    q[++tl] = k;
                    ++k;
                }
                if (fr <= tl)  dp[i][j] = max(dp[i][j],dp[q[fr]][j] + sum[i] - sum[q[fr]]);
            }
        }
        printf("%d\n",dp[n][m]);
    }
    return 0;
}