单调队列优化DP

做了几道前几天多校的单调队列优化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了。。无语了。。

View 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即可。

View 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对应的值算出来维护个单调队列

卖一样;

View 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都是相同的,这样就保证了我们单调队列里面的值不会改变。哎,这样的优化题目还是不熟啊。

View 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;

}

你可能感兴趣的:(优化)