问题
给定正整数d 和n 个正整数 ,除了 不能修改,其他的 都能修改,修改后要求前后两个数之差的绝对值小于等于d,修改后的 是 ,问满足条件的最小 的值是多少
分析
动态规划的难点在于分析问题,找出状态和转移方程
本题是多状态决策过程,依此确定每个
修改成什么值,由于d和h的值都非常大,他们不能直接作为状态,数组开不下,所以状态应该是d(i,j),i代表是前i个数,j代表i所在的高度,d(i,j)代表到现在的花费,j一定是经过选择的,不可能是
,到现在就是我的思路了,然后就断掉了
下面是紫书上的分析,j的取值是前一个值的顶部,底部,或者不改变它本身,所以就有3种选择,所以j的选择有 共 种,所以d(i,j)共有 种状态
然后对于每一种状态求值,使用单调队列简化计算,每个状态平均时间
,所以总的时间复杂度
状态转移方程
,如果从小到大的顺序计算j,那么j-d<=k<=j+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;
}