給一組數(高度)h1,h2,...,hn,要求修改h2~h(n-1)的值,使得h1,h2,...,hn中任意相鄰兩個數的差的絕對值不超過d。將某個hx從a修改到b的代價為|a-b|,求最小總代價。
n<=100 d<=1e9
基本思路是常規的動態規劃,用dp[i][j]
表示考慮前i個高度,并且第i個高度修改為j時的最小代價。但是這樣j的范圍太大,所以標準解答將dp的第二個維度離散化了。
可以證明這樣的結論:在最優解中,任何一個高度都可以寫成的形式,hx是h1~hn中的某個高度,k是(-2n,2n)之間的整數。
下面證明它。
假設上圖是最優解中某個任意位置x以及它左右的一些高度。先說明一下這個圖,每個方塊的中心表示它的高度,方塊邊長為d,相鄰方塊均相連從而相鄰高度差不超過d。
我們以x為中心向兩邊擴展所有只以1個點相連的其他高度,當出現相鄰兩個方塊的公共部分是線段時就停下,也就是我們只考慮圖中那些實線方塊。
如果這一組方塊中有任意一個處在自己原先的位置,那么x的位置顯然符合結論。如果它們全部都不在自己原先的位置,那么我們可以把這個整體向上或向下移動微小距離而保證整體代價不增加,這個過程持續到其中任意一個方塊移回原始位置或者虛線方塊被納入實線方塊內。至此,結論的正確性顯而易見。
如此狀態數是O(n3),但轉移似乎也要n2,時間復雜度承受不起。可以在計算dp(i,Hj)
時按照Hj(Hj是一系列離散值)從小到大的順序來算,由于dp(i,Hj)=|h[i]-Hj|+min{dp(i-1,Hx)|Hx∈[Hj-d,Hj+d]}
,所以相當于計算Hx在區間[Hj-d,Hj+d]上dp(i-1,Hx)的最小值,可以用滑動窗口+單調隊列來處理,從而使轉移復雜度變成平攤后O(1)。
AC代碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define y second
#define x first
#define LL long long
#define INF 1e15
using namespace std;
const int maxn=100+5;
LL n,d,h[maxn],dp[maxn][2*maxn*maxn],f[2*maxn*maxn];
int fcnt;
pair<LL,int> q[2*maxn*maxn];
void solve(){
fcnt=0;
for(int i=0;i<n;i++)
for(int k=-n+1;k<=n-1;k++)
f[fcnt++]=h[i]+k*d;
sort(f,f+fcnt);
for(int j=0;j<fcnt;j++){
dp[0][j]=INF;
}
int j0,je;
for(int i=0;i<fcnt;i++){
if(f[i]==h[0])j0=i;
if(f[i]==h[n-1])je=i;
}
dp[0][j0]=0;
for(int i=1;i<n;i++){
int L=0,R=0;//[L,R)
int ft=0,rr=0;//[ft,rr)
for(int j=0;j<fcnt;j++){
while(f[L]<f[j]-d){
if(L==q[ft].y)ft++;
L++;
}
while(R<fcnt&&f[R]<=f[j]+d){
LL cur=dp[i-1][R];
while(rr>ft&&cur<=q[rr-1].x)rr--;
q[rr++]=make_pair(cur,R);
R++;
}
if(q[ft].x!=INF){
dp[i][j]=abs(f[j]-h[i])+q[ft].x;
}else dp[i][j]=INF;
}
}
if(dp[n-1][je]==INF)printf("impossible\n");
else printf("%lld\n",dp[n-1][je]);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&n,&d);
for(int i=0;i<n;i++)scanf("%lld",&h[i]);
solve();
}
}