好吧,不会做
一开始想到的状态是定义dp[i][j]表示第i个数修改为j时的最小值,然后因为d范围过大就把这个想法否定掉了,然后就不会做了
否定的理由是①:数组开不下②:对于一个j要考虑[j-d,j+d]之间的数,状态太多了,状态总数显然是Ω(n*d)的
确实没有想到通过分析来减少状态的数量,而且是大大减少
首先来确定impossible的情况,无妨设h1<hn,我们从左向右考虑,考虑impossible时hn的最小值
显然h2的最大值只能是h1+d,h3的最大值只能是h2+d即h1+2d........h(n-1)的最大值是h1+(n-2)d,hn的最大值是h1+(n-1)d
我们在上述增加的过程中,每次都增加了最大的增量,因此hn最大不能超过h1+(n-1)d,假若超过了这个值,便无论如何也加不到了,便是impossible
接下来考虑减少状态,即对于中间的hj,我们考虑能把它改到哪些位置
突然发现数学归纳法真是一个比较好的办法——仅仅在证明的意义下
当仅仅有h1,h2,h3时,中间的h2只有3个状态,即可以写成hp+k*d的形式,其中p∈[1,n],k∈(-n,n)
归纳猜想是有k个数时,里面每一个都能写成hp+k*d的形式
当向里面增加一个数时,无妨设加到i位置上,因此hi可以变成hi,max(h(i-1),h(i+1))+d,min(h(i-1),h(i+1))-d,也就是说,hi同样的也可以写成hp+k*d的形式
我觉得这个证明是没错的。。。
至于问为什么想到的。。。我也不能回答你
第二件事:状态转移方程
显然dp[i][j]=|hi-j|+min(dp[i-1][k])其中j-d<=k<=j+d
单调队列嘛,看着代码很好理解的,我把我想不到的重点说完了,这里就自己看代码好了,不过老实说我就算想到了减状态也不会想到单调队列。
单调队列优化挺大的
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
using ll=long long;
const int maxm=105;
const ll INF=(1LL<<60);
ll times,n,d,h[maxm],nx,t,dp[2][maxm*maxm*2],x[maxm*maxm*2];
void pretreatment();
int main(){
ios_base::sync_with_stdio(false);
cin>>times;
while(times--){
cin>>n>>d;
for(int i=0;i<n;++i)
cin>>h[i];
if(labs(h[0]-h[n-1])>(n-1)*d){
cout<<"impossible\n";
continue;
}
pretreatment();
for(int i=1,k=0;i<n;++i,k=0,t^=1)
for(int j=0;j<nx;++j){
while(k<nx&&x[k]<x[j]-d)++k;
while(k+1<nx&&x[k+1]<=x[j]+d&&dp[t][k+1]<=dp[t][k])++k;
dp[t^1][j]=(dp[t][k]==INF)?INF:labs(h[i]-x[j])+dp[t][k];
}
for(int i=0;i<nx;++i)
if(x[i]==h[n-1])
cout<<dp[t][i]<<endl,i=nx;
}
return 0;
}
void pretreatment(){
nx=0;
for(int i=0;i<n;++i)
for(int j=-n+1;j<=n-1;++j)
x[nx++]=h[i]+j*d;
sort(x,x+nx);
nx=unique(x,x+nx)-x;
for(int i=0;i<nx;++i){
dp[t][i]=INF;
if(h[0]==x[i])
dp[t][i]=0;
}
}