#bzoj2237#龙珠雷达(Dp + 单调队列优化)

31 篇文章 0 订阅
27 篇文章 0 订阅

2237: 龙珠雷达

时间限制: 1 Sec  内存限制: 128 MB

题目描述

 你得到了一个龙珠雷达,它会告诉你龙珠出现的时间和地点。

 龙珠雷达的画面是一条水平的数轴,每一个窗口时间,数轴的某些点上会出现同一种龙珠,每当你获得其中一颗龙珠,其它龙珠就会消失。下一个窗口时间,数轴上又会出现另一种龙珠。总共有n个窗口时间,也就是总共有n种龙珠。

假设你会瞬间移动,你从数轴的x点移动到y点,耗时0秒,但是需要耗费|x-y|的体力。同时,挖出一颗龙珠也需要耗费一定的体力。请问,最少耗费多少体力,就可以收集齐所有种类的龙珠。

输入

第一行,三个整数n,m,x,表示共有n个窗口时间,每个窗口时间会出现m个龙珠,x是一开始你所处的位置。

    接下来有两个n*m的矩阵。

    对于第一个矩阵,坐标为(i,j)的数字表示第i个窗口时间,第j个龙珠的位置。

    对于第二个矩阵,坐标为(i,j)的数字表示第i个窗口时间,挖取第j个龙珠所需的体力。

输出

 一个整数,表示所需最小体力

样例输入

3 2 52 34 11 31 11 34 2

样例输出

8

提示

所有数据均为整数

数轴范围在030000

挖一颗龙珠所需体力不超过30000

结果保证在int范围

对于50%的数据,1<=n<=50,1<=m<=500。

对于100%的数据,1<=n<=50,1<=m<=5000。


很显然的DP题,状态和转移都不难想。

定义:Dp[i][j]:到了第i个时刻(第i种龙珠),挖第j个所需要的最小体力,答案为Dp[N][j]中最小的

Dp[i][j] = min( Dp[i - 1][k] + dis( Pos(i - 1, k), Pos(i, j) ) ) + Cost[i][j];


如果不优化的话,写成这样(我把第一维压缩了,因为只与上一层状态有关):

int flg = 1;
    for(int i = 1; i <= M; ++ i)    Dp[flg][i] = INF;
    for(int i = 1; i <= M; ++ i)
        Dp[flg][i] = min(Dp[flg][i], abs(Pos[flg][i] - X) + Cost[flg][i]);
    for(int i = 2; i <= N; ++ i){//50
        flg ^= 1;
        for(int j = 1; j <= M; ++ j){//5000
            Dp[flg][j] = INF;
            for(int k = 1; k <= M; ++ k)
                Dp[flg][j] = min(Dp[flg][j], Dp[flg ^ 1][k] + abs(Pos[i - 1][k] - Pos[i][j]));
            Dp[flg][j] += Cost[i][j];
        }
    }
    int Ans = INF;
    for(int i = 1; i <= M; ++ i)
        Ans = min(Ans, Dp[flg][i]);

但是算算时间,12亿多,肯定超了,于是开始优化。

先把abs()打开:

①Dp[ i ][ j ] = Dp[ i - 1 ][ k ] - Pos[ i - 1 ][ k ] + Pos[ i ][ j ] + Cost[ i ][ j ] (Pos[i][j] >= Pos[i - 1][k])

②Dp[ i ][ j ] = Dp[ i - 1 ][ k ] + Pos[ i - 1 ][ k ] - Pos[ i ][ j ] + Cost[ i ][ j ] (Pos[i][j] <= Pos[i - 1][k])

第一反应就应该是线段树,用pos做树的下标,开两棵树,

一棵存①(Dp[i - 1][k] - Pos[i - 1][k]),一棵存②(Dp[i - 1][k] + Pos[i - 1][k]),

每次在P[i][j], ①在其左边找最小,②在其右边找最小,然后合起来比较取最小值

记得对每个i都要清一次树,建一次树。

然后我就没写了,懒,这个是可以过的,就是会慢很多。


这个的正确姿势是单调队列,开两个单调队列,存法同上。

不过需要注意的是,对于①,单调队列中应维护pos值从大到小,算式值单调递增。

因为要找小于pos[i][j]的最小值,所以求得时候,Dp中j也应从大到小枚举。

对于②,pos从小到大,算式值单调递增,Dp时直接塞。

注意判断队列是否为空。

#include<iostream>//Dp[i][j]:到了第i个时刻(第i种龙珠),挖第j个所需要的最小体力
#include<cstdio>//Dp[i][j] = min(Dp[i - 1][k] + dis(Pos(i - 1, k), Pos(i, j))) + Cost[i][j];
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;

const int Maxn = 50;
const int Maxm = 5000;
const int Maxw = 30000;
const int INF = 0x7f7f7f7f;

struct node{
    int val, pos;
    node(){}
    node(int a, int b){ val = a, pos = b;}
    bool operator < (const node & X) const{
        return pos < X.pos;
    }
}P[Maxn + 5][Maxm + 5];

int N, M, X;
int Dp[2][Maxm + 5];

void getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9')  if(c == '-')    flg = -1;
    while(c >= '0' && c <= '9'){    num = num * 10 + c - 48;    c = getchar();}
    num *= flg;
}

deque<node>Q[2];

int main(){
    //freopen("long.in", "r", stdin);
    //freopen("long.out", "w", stdout);
    getint(N), getint(M), getint(X);
    for(int i = 1; i <= N; ++ i)
        for(int j = 1; j <= M; ++ j)
            getint(P[i][j].pos);
    for(int i = 1; i <= N; ++ i){
        for(int j = 1; j <= M; ++ j)
            getint(P[i][j].val);
        sort(P[i] + 1, P[i] + 1 + M);
    }
    P[0][1].pos = X;
    int flg = 0;
    for(int i = 1; i <= M; ++ i)    Dp[flg][i] = INF;
    Dp[flg][1] = 0;
    for(int i = 1; i <= N; ++ i){
        Q[0].clear(), Q[1].clear();
        for(int j = M; j >= 1; -- j){
            while(! Q[0].empty() && Q[0].back().val > Dp[flg][j] - P[i - 1][j].pos) Q[0].pop_back();
            Q[0].push_back(node(Dp[flg][j] - P[i - 1][j].pos, P[i - 1][j].pos));
        }
        for(int j = 1; j <= M; ++ j){
            while(! Q[1].empty() && Q[1].back().val > Dp[flg][j] + P[i - 1][j].pos) Q[1].pop_back();
            Q[1].push_back(node(Dp[flg][j] + P[i - 1][j].pos, P[i - 1][j].pos));
        }
        flg ^= 1;
        bool ff = 0;
        for(int j = M; j >= 1; -- j){
            Dp[flg][j] = INF;
            while(! Q[0].empty() && Q[0].front().pos > P[i][j].pos) Q[0].pop_front();
            if(! Q[0].empty())
                Dp[flg][j] = min(Dp[flg][j], Q[0].front().val + P[i][j].pos + P[i][j].val), ff = 1;
        }
        for(int j = 1; j <= M; ++ j){
            if(! ff)    Dp[flg][j] = INF;
            while(! Q[1].empty() && Q[1].front().pos < P[i][j].pos) Q[1].pop_front();
            if(! Q[1].empty())
                Dp[flg][j] = min(Dp[flg][j], Q[1].front().val - P[i][j].pos + P[i][j].val);
        }
    }
    int Ans = INF;
    for(int i = 1; i <= M; ++ i)
        Ans = min(Ans, Dp[flg][i]);
    printf("%d\n", Ans);
    return 0;
}






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值