UVA - 12170 Easy Climb 单调队列优化动态规划

问题

给定正整数d ( 0 < = d < = 1 e 9 ) (0<=d<=1e^9) 和n ( 2 < = n < = 100 ) (2<=n<=100) 个正整数 h 1 , h 2 , h n ( 0 < = h i < = 1 e 9 ) h_1,h_2,···h_n(0<=h_i<=1e^9) ,除了 h 1 , h n h_1,h_n 不能修改,其他的 h i h_i 都能修改,修改后要求前后两个数之差的绝对值小于等于d,修改后的 h i h_i h i h_i' ,问满足条件的最小 i = 2 n 1 h i h i \sum_{i=2}^{n-1}|h_i'-h_i| 的值是多少

分析

动态规划的难点在于分析问题,找出状态和转移方程
本题是多状态决策过程,依此确定每个 h i h_i 修改成什么值,由于d和h的值都非常大,他们不能直接作为状态,数组开不下,所以状态应该是d(i,j),i代表是前i个数,j代表i所在的高度,d(i,j)代表到现在的花费,j一定是经过选择的,不可能是 0 1 e 9 0-1e9 ,到现在就是我的思路了,然后就断掉了

下面是紫书上的分析,j的取值是前一个值的顶部,底部,或者不改变它本身,所以就有3种选择,所以j的选择有 h p + k d 1 < = p < = n , n < k < n h_p+kd,1<=p<=n,-n<k<n O ( n 2 ) O(n^2) 种,所以d(i,j)共有 O ( n n 2 ) = O ( n 3 ) O(n*n^2)=O(n^3) 种状态

然后对于每一种状态求值,使用单调队列简化计算,每个状态平均时间 O ( 1 ) O(1) ,所以总的时间复杂度 O ( n 3 ) O(n^3)
状态转移方程 d p ( i , j ) = h i j + m i n ( d p ( i 1 , k ) j d < = k < = j + d ) dp(i,j)=|h_i-j|+min(dp(i-1,k)|j-d<=k<=j+d) ,如果从小到大的顺序计算j,那么j-d<=k<=j+d就相当于是一个宽度是 2 d 2*d ,中心是k的滑动窗口,所以可以使用单调队列简化

紫书代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <utility>
using namespace std;
const long long Inf=(1LL<<60);
const int maxn=105,maxl=maxn*maxn*2;
int kase,n;
//使用滚动数组节省空间,不然空间会很大,q是单调队列,front,tail是单调队列的首部,尾部
long long d,h[maxn],dp[2][maxl],value[maxl],nv,front,tail,q[maxl];  //nv:number of value

int main(void){
    cin>>kase;
    while(kase--){
        cin>>n>>d;
        for(int i=0;i<n;++i){
            cin>>h[i];
        }
        if(abs(h[0]-h[n-1])>(n-1)*d){
            printf("impossible\n");
            continue;
        }
        nv=0;
        for(int i=0;i<n;++i){
            for(int j=-n+1;j<n;++j){
                value[nv++]=h[i]+j*d;
            }
        }
        sort(value,value+nv);  //排序
        nv=unique(value,value+nv)-value;   //去重,改变nv的值
        long long k=0,t=0;
        for(int i=0;i<nv;++i) {
            dp[0][i] = Inf;   //初始化
            if(value[i]==h[0]) dp[0][i]=0;  //第一个h1就是它本身,不用改变
        }
        for(int i=1;i<n;++i){
            k=0;
            front=tail=0;
            for(int j=0;j<nv;++j){
                while(k<nv && value[k]<value[j]-d) ++k;
                while(k<nv && value[k]<=value[j]+d){
                    while(tail>front && value[q[front]]<value[j]-d) ++front;
                    if(tail==front){ //q为空,直接入队
                        q[tail++]=k;
                    }else if(dp[t][k]<=dp[t][q[tail-1]]){
                        //dp[t][k]更小,那就将前面的出队
                        while(tail>front && dp[t][k]<=dp[t][q[tail-1]]) --tail;
                        q[tail++]=k;
                    }else q[tail++]=k;
                    ++k;
                }
                if(dp[t][q[front]]==Inf) dp[t^1][j]=Inf;
                else dp[t^1][j]=abs(h[i]-value[j])+dp[t][q[front]];
            }
            t=t^1;
        }
        for(int i=0;i<maxl;++i){
            if(value[i]==h[n-1]){
                printf("%lld\n",dp[t][i]);
                break;
            }
        }
    }
    return 0;
}

发布了15 篇原创文章 · 获赞 0 · 访问量 171

猜你喜欢

转载自blog.csdn.net/zpf1998/article/details/103974036
今日推荐