DP优化小结

近期打算整理一下DP的优化部分, 主要参照HH的博客顺序HH关于DP优化进行训练,本篇将持续更新

单调队列优化DP

斜率优化DP

平行四边形优化

滚动数组优化(空间优化)

数据结构优化DP(线段树等)


单调队列


单调队列优化DP:

HDOJ3401

题意:

分析:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxab(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
const int N = 2010;
const int INF = 0x3f3f3f3f;
int f[N][N];
int qu[N], pos[N];

int main(){
    int T;
    int n, maxp, w;
    int Ap, Bp, As, Bs;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d%d", &n, &maxp, &w);
        for(int i = 1; i <= w + 1; ++i){
            scanf("%d%d%d%d", &Ap, &Bp, &As, &Bs);
            for(int j = 0; j <= maxp; ++j){
                if(j <= As) f[i][j] = -Ap*j;
                else f[i][j] = -INF;
                if(i > 1 && f[i - 1][j] > f[i][j])
                    f[i][j] = f[i - 1][j];
            }
        }

        for(int i = w + 2; i <= n; ++i){
            scanf("%d%d%d%d", &Ap, &Bp, &As, &Bs);
            int p = 0, q = -1;
            int k = i - w - 1;

            for(int j = 0; j <= maxp; ++j){
                int tmp = f[k][j] + j*Ap;
                while(p <= q && qu[q] < tmp){
                    --q;
                }
                qu[++q] = tmp;
                pos[q] = j;

                while(j - pos[p] > As) ++p;
                f[i][j] = maxab(f[i - 1][j], qu[p] - j*Ap);
            }

            p = 0, q = -1;
            for(int j = maxp; j >= 0; --j){
                int tmp = f[k][j] + j*Bp;
                while(p <= q && qu[q] < tmp){
                    --q;
                }
                qu[++q] = tmp;
                pos[q] = j;

                while(pos[p] - j > Bs) ++p;
                f[i][j] = maxab(f[i][j], qu[p] - j*Bp);
            }
        }
        printf("%d\n", f[n][0]);
    }
    return 0;
}


NOI2005瑰丽的华尔兹

题意:这道题目很有趣, 以电影《海上钢琴师》的1900作为题目背景。大概的意思是:在m*n的区域内, 有些地方能碰, 有些地方不能碰。有k个时间段, 每个时间段内, 可能会做出上下左右四个方向的移动。给出初始位置, 以及K各时间段的信息, 求出最大能够移动的距离。

分析:

1. 首先确定是DP,设f[k][x][y]表示:第K个时间区间结束之后,在[x, y]位置能够移动的最大距离。

2. f[k][x][y] = max(f[k-1][x1][y1] + dist)         比如k个区间是做向上的操作, 那么f[k][x][y] = max(f[k-1][x1][y] + (x1 - x)) (x1 - x <= T)    T表示时间段[s,e]的长度 T=e-s+1;  时间复杂度最大可到O(k*max(m*n^2, n*m^2)), 初始状态为f[0][x][y] = 0。

3. 跟上题一样, 我们可以考虑对f[k-1][x1][y1] + x1这样的状态进行单调队列的优化。具体方法与上题一模一样。 这样就可以优化掉决策里面的一维, 最大的时间复杂度:O(k*mn); 同时注意用滚动数组对空间进行优化。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210;
const int INF = 0x3f3f3f3f;
char s[N][N];
int f[2][N][N];
int dq[N], pos[N];
int fx[4] = {-1, 1, 0, 0};
int fy[4] = {0, 0, -1, 1};
int m, n;

void gao(int k, int x, int y, int size, int w, int d){
    int p = 0, q = -1;
    for(int i = 1; i <= w; ++i){
        if(s[x][y] == '.'){
            int tmp = f[(k-1) & 1][x][y] - i;
            while(p <= q && dq[q] < tmp) --q;
            dq[++q] = tmp, pos[q] = i;

            while(i - pos[p] > size){
                ++p;
            }

            f[k & 1][x][y] = dq[p] + i;
        }
        else{
            p = 0, q = -1;
            f[k & 1][x][y] = -INF;
        }
        x += fx[d], y += fy[d];
    }
}

int main(){
    int x, y, k, d;
    while(~scanf("%d %d", &m, &n)){
        scanf("%d %d %d", &x, &y, &k);
        for(int i = 0; i < m; ++i){
            scanf("%s", s[i]);
        }
        for(int i = 0; i < m; ++i)
            for(int j = 0; j < n; ++j){
                f[0][i][j] = -INF;
            }
        f[0][x - 1][y - 1] = 0;

        for(int i = 1; i <= k; ++i){
            scanf("%d %d %d", &x, &y, &d);
            if(d == 1){
                for(int j = 0; j < n; ++j){
                    gao(i, m - 1, j, y - x + 1, m, 0);
                }
            }
            else if(d == 2){
                for(int j = 0; j < n; ++j){
                    gao(i, 0, j, y - x + 1, m, 1);
                }
            }
            else if(d == 3){
                for(int j = 0; j < m; ++j){
                    gao(i, j, n - 1, y - x + 1, n, 2);
                }
            }
            else{
                for(int j = 0; j < m; ++j){
                    gao(i, j, 0, y - x + 1, n, 3);
                }
            }
        }

        int ans = 0;
        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                if(f[k&1][i][j] > ans) ans = f[k&1][i][j];
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值