每次的调整都要依赖于上一次的决策,因此是一个多阶段决策问题。多节段决策如果不用打印解,最好用滚动数组优化
设 dp(i,j) 表示将第i个山坡高度改为j,还需要修改前i-1个山坡,所需要的代价总合。
然而 j 的可能性太多(d<10^9),因此这个状态的设计是不可行的。不过n的值却很小(n<100),因此可以从n上下手
深入分析问题,发现如果前后两个山坡的高度已经固定了,那么当前山坡调整后的高度应该属于[ max(h[i-1], h[i+1])-d , min(h[i-1], h[i+1])+d ]。如果这个区间为空则无解。如果山坡已经属于这个高度则不用调整,否则应调整到就近的边界值。
将这种情况扩展一下,可以发现改变后的山坡高度总能表示为h[p] + q*d,p属于[0,n-1],q属于(-n,n)。因此有用的高度值其实只有n*2n个。将所有高度值的可能性算出来存在数组x中,并去重和排序。设dp(i,j)表示将第i个山坡的高度改为x[j],还需要修改前i-1个山坡,所需要的代价总合。
转移方程:dp(i,j) = abs(h[i]-x[j]) + min{dp(i-1,y)},x[y]属于[x[j]-d, x[j]+d]。这里的各种定义有点绕。有些是根据数组x的下标定义,有些是直接根据山坡高度定义。需要谨慎区分。
当j固定时,y的定义域则是0-(n-1)上的一段连续子区间。当j从0移动到n-1时,y就形成了一个滑动窗口。只不过这个滑动窗口的左右边界的更新与之前的有些不同,并不是j每次移动都会固定的出一个、入一个,而是应进行判断。对于每个j,我们要取出y的定义域内dp[i-1][y]的最小值来更新dp[i][j]。
Rujia的滚动数组很好用。t和t^1的设计使得滚动数组的滚动方向不再受到限制。至于滑动窗口的实现,我直接用了普通的单调队列,而Rujia的代码中巧妙的使用了一个k,对数据特征进行了进一步剖析。
Run Time: 0.768s
#define UVa "LT9-25.12170.cpp" //Easy Climb
char fileIn[30] = UVa, fileOut[30] = UVa;
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
//Global Variables. Reset upon Each Case!
typedef long long LL;
const int maxn = 100 + 5, maxx = 2*maxn*maxn;
const LL INF = (1LL)<<60;
int T, n, nx;
LL d, h[maxn], x[maxx], dp[2][maxx];
/
struct Monoqueue {
LL q[maxx];
int front, rear;
Monoqueue():front(-1),rear(-1){}
void maintain_left(LL v) {
if(front != -1 && q[rear] == v) rear++;
}
void insert(LL v) {
if(front == -1) {
rear = front = 0;
q[front] = v;
}
else {
while(q[front] > v && front >= rear) front --;
q[++front] = v;
}
}
LL get_min() {
return q[rear];
}
};
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &d);
for(int i = 0; i < n; i ++) scanf("%lld", &h[i]);
if(abs(h[0] - h[n-1]) > (n-1)*d) {
cout << "impossible\n";
continue;
}
nx = 0;
for(int i = 0; i < n; i ++)
for(LL j = -n+1; j < n; j ++)
x[nx++] = h[i]+j*d;
sort(x, x + nx);
nx = unique(x, x+nx) - x;
int t = 0;
for(int i = 0; i < nx; i ++) {
if(x[i] == h[0]) dp[0][i] = 0;
else dp[0][i] = INF;
}
for(int i = 1; i < n; i ++) {
Monoqueue mq = Monoqueue();
int p1 = 0, p2 = 0; //p1 points to first element >= x-d. p2 points to first element > x+d
for(int j = 0; j < nx; j ++) {
while(p1 < nx && x[p1] < x[j] - d) {
mq.maintain_left(dp[t][p1]); //pop out from monoqueue.
p1 ++;
}
if(p2 < p1) p2 = p1;
while(p2 < nx && x[p2] <= x[j] + d) {
mq.insert(dp[t][p2]); //insert into monoque.
p2 ++;
}
if(mq.get_min() == INF) dp[t^1][j] = INF;
else dp[t^1][j] = abs(x[j]-h[i]) + mq.get_min();
}
t^=1;
}
for(int i = 0; i < nx; i ++)
if(x[i] == h[n-1]) cout<<dp[t][i]<<endl;
}
return 0;
}