题意:输入正整数d和n个正整数h1,h2,...,hn。可以修改除了h1和hn的其他数,要求修改后相邻两个数之差的绝对值不超过d,且修改费用最小。设hi修改后的值为hi',则修改费用为|h1-h1'|+|h2-h2'|+...+|hn-hn'|;
无解输出impossible,有解输出最小费用。
(2 ≤ n ≤ 100)(0 ≤ d ≤ 10^9)(0 ≤ hi ≤ 10^9)
难得想,我真想问那些写题解的:通过枚举可以发觉每个数字只能变成a_i+k*d这种形式的数字
到底怎么枚举才能把这么复杂的规律枚举出来?
必须用单调队列优化,因为不用时间复杂度是O(n^5),用了时间复杂度几乎是O(n*(n^2))=O(n^3);
/**==========================================
* This is a solution for ACM/ICPC problem
*
* @source:uva 12170 Easy Climb
* @type: dp
* @author: wust_ysk
* @blog: http://blog.csdn.net/yskyskyer123
* @email: 2530094312@qq.com
*===========================================*/
#define REP(i,n) for(int i=0 ;i<(n) ;i++)
#define ysk(x) (1<<(x))
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn= 100 ;
const ll INF= (1ll<<60 ) ;
ll h[maxn+3],dp[maxn+3][2*(maxn*maxn)+10],d;
int n,m;
vector<ll >ve;
int q[2*(maxn*maxn)+3];
void init()
{
ve.clear();
REP(i,n)
{
REP(j,n)
{
ve.push_back(h[i]+j*d);
ve.push_back(h[i]-j*d);
}
}
sort(ve.begin(),ve.end());
m=unique(ve.begin(),ve.end())-ve.begin();//unique将重复的元素放在后面
}
void work()
{
REP(j,m)
{
if(ve[j]==h[0]) dp[0][j]=0;
else dp[0][j]=INF;
}
for(int i=1;i<n;i++)
{
int k=0,rear=0,front=0;
for(int j=0;j<m;j++)
{
while(front<rear&& ve[q[front]]<ve[j]-d ) front++;
while(k<m&&ve[k]<ve[j]-d) k++;
while(k<m&&ve[k]<=ve[j]+d)
{
while(front<rear&&dp[i-1][q[rear-1]]>=dp[i-1][k] ) rear--;
q[rear++]=k++;
}
ll mini=dp[i-1][q[front] ];
if(mini!=INF) dp[i][j]=mini+abs(h[i]- ve[j] );
else dp[i][j]=INF;
}
}
REP(i,m)
{
if(ve[i]==h[n-1])
{
printf("%lld\n",dp[n-1][i]);
break;
}
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%lld",&n,&d);
REP(i,n)
{
scanf("%lld",&h[i]);
}
if(abs(h[0]-h[n-1] ) >(n-1)*d )
{
puts("impossible");
continue;
}
init();
work();
}
return 0;
}