题意,给定n(<=70)座山的高度和d(<=1e9),每座山可以花x的代价使高度增加x或减少x,使得两座相邻的山高度差<=d,求最小代价,无解输出impossible
首先大致想到,可以用f(i,k)表示已经调整完了第i座山,第i座山调整成了高度k,花费的最小代价
但是很明显,k的范围是1e9,显然不通,所以就要想办法简化这一维
当n=3,只能调整第二座山,第二座山调整后的范围应为(h1-d,h1+d)与(h3-d,h3+d)的交集(max(h1,h3)-d,min(h1,h3)+d),再思考一下是不是这个区间里的数都要考虑呢?最优只有三种决策,当h2在范围中,不调整。当h2<下界,调整至下界。当h2>上界,调整至上界。
所以推广一下,每座山的高度就是(hi(1<=i<=n)+kd(-n<k<n)),虽然我也不会证明,这样就预先n^2把这些值处理出来,拍一下序,就相当于把d离散化到n^2的数量级,dp[i][x]代表第x大的d值(记得去重,以及把负数删掉)
在考虑转移时,dp[i][x]就是在dp[i-1][x-d---x+d]中找一个最小值转移,再加上常数|h[i]-x|,这样就可以用优先队列来优化。看一个dalao的代码,发现其实可以不用单调队列。借用这个思想,去重后设有m个d值,对于一个固定的i,dp[i][1--m]是一个单峰有最小值函数。对于一个一个区间内肯定单调或单峰。
优先队列:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF (2100000000000000)
using namespace std;
int H[200],n,d,m;
LL A[200*400],dp[2][250*250];
struct Node{
int Hei;
LL val;
Node(){}
Node(int hei,LL V){
Hei=hei; val=V;
}
}Q[2001010];
LL Abs(LL a){return a<0?-a:a;}
void Work(){
m=0; int i,j;
scanf("%d %d",&n,&d);
for (i=1;i<=n;i++) scanf("%d",&H[i]);
for (i=1;i<=n;i++) for (j=-n+1;j<n;j++){
A[++m]=(LL)H[i]+(LL)d*(LL)j;
if (A[m]<0) m--;
}
sort(A+1,A+m+1);
m=unique(A+1,A+m+1)-A-1;
for (i=1;i<=m;i++){
dp[1][i]=dp[0][i]=INF;
if (H[1]==A[i]) dp[0][i]=0;
}
int cur=0,pre=1;
for (i=2;i<=n;i++){
swap(cur,pre);
int front=1,tail=0,k=1;
for (j=1;j<=m;j++){
while (k<=m&&Abs(A[k]-A[j])<=d){
while (tail>=front&&dp[pre][k]<=Q[tail].val) tail--;
Q[++tail]=Node(A[k],dp[pre][k]);
k++;
}
while (tail>=front&&Abs(Q[front].Hei-A[j])>d) front++;
if (front>tail||Q[front].val==INF) dp[cur][j]=INF;
else dp[cur][j]=Q[front].val+Abs(A[j]-H[i]);
}
}
for (i=1;i<=m;i++)
if (A[i]==H[n]){
if (dp[cur][i]>=INF) printf("impossible\n");
else cout<<dp[cur][i]<<endl;
return;
}
}
int main(){
int Test_case;
scanf("%d",&Test_case);
while (Test_case--) Work();
return 0;
}简化:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF (2100000000000000)
using namespace std;
int H[200],n,d,m;
LL A[200*400],dp[2][250*250];
LL Abs(LL a){return a<0?-a:a;}
void Work(){
m=0; int i,j;
scanf("%d %d",&n,&d);
for (i=1;i<=n;i++) scanf("%d",&H[i]);
for (i=1;i<=n;i++) for (j=-n+1;j<n;j++){
A[++m]=(LL)H[i]+(LL)d*(LL)j;
if (A[m]<0) m--;
}
sort(A+1,A+m+1);
m=unique(A+1,A+m+1)-A-1;
for (i=1;i<=m;i++){
dp[1][i]=dp[0][i]=INF;
if (H[1]==A[i]) dp[0][i]=0;
}
int cur=0,pre=1;
for (i=2;i<=n;i++){
swap(cur,pre);
int k=1;
for (j=1;j<=m;j++){
while (k+1<=m&&Abs(A[k]-A[j])>d) k++;
//如果k在合法区域外,移到合法区域下界
while (k+1<=m&&Abs(A[k+1]-A[j])<=d&&dp[pre][k]>=dp[pre][k+1]) k++;
//找到极值处 //上面是>=不是>
if (Abs(A[k]-A[j])>d||dp[pre][k]==INF) dp[cur][j]=INF;
else dp[cur][j]=dp[pre][k]+Abs(A[j]-H[i]);
}
}
for (i=1;i<=m;i++)
if (A[i]==H[n]){
if (dp[cur][i]>=INF) printf("impossible\n");
else cout<<dp[cur][i]<<endl;
return;
}
}
int main(){
int Test_case;
scanf("%d",&Test_case);
while (Test_case--) Work();
return 0;
}
针对给定数量的山脉和最大允许高度差,通过预处理和动态规划算法确定将所有相邻山脉高度差限制在指定范围内的最小调整成本。采用离散化技术和优先队列优化策略实现高效求解。
1377

被折叠的 条评论
为什么被折叠?



