首先考虑最简朴的dp。
表示第
个人身高为
的最小花费。显然这个值只与它前面一个人的身高有关。枚举前一个人身高
,枚举当前人身高
,则有转移方程:
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+10;
const int maxm=1e2+10;
const int oo=1<<30;
int n,c,h[maxn],dp[maxn][maxm],tmp=oo,ans=oo;
int main(){
while(scanf("%d%d",&n,&c)==2){
ans=oo;
for(int i=1;i<=n;++i) scanf("%d",&h[i]);
for(int a=h[1];a<=100;++a) dp[1][a]=(h[1]-a)*(h[1]-a);
for(int i=2;i<=n;++i){
for(int a=h[i];a<=100;++a){
dp[i][a]=oo;
for(int b=h[i-1];b<=100;++b)
dp[i][a]=min(dp[i][a],dp[i-1][b]+c*abs(a-b)+(h[i]-a)*(h[i]-a));
}
}
for(int a=h[n];a<=100;++a) ans=min(ans,dp[n][a]);
if(n!=0) printf("%d\n",ans);
}
}
用单调队列优化。观察转移方程。
发现有一个绝对值。分类讨论。如果
,那么原式可以化为:
发现当
,
确定的时候,
和
是确定的。于是把原式分成两个部分。
令:
于是
。
数组便可以用一个单调队列来维护。具体实现见代码。
另外,不要忘了身高不能低于原始身高,因为身高只可增不可降。。。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
const int oo=1<<30;
int dp[2][maxn],q[maxn];
int n,c,now,head,tail,h,f,ans;
inline void print(int x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int main(){
while(scanf("%d%d",&n,&c)==2){
ans=oo,now=0,scanf("%d",&h);
for(int i=0;i<h;++i) dp[now][i]=oo;
for(int i=h;i<=100;++i) dp[now][i]=(i-h)*(i-h);
for(int i=2;i<=n;++i){
scanf("%d",&h),head=tail=0,now^=1;
for(int j=0;j<=100;++j){
//相当于是枚举当前人的身高为j,而j的枚举顺序(从小到大)又限制了前一个人的身高小于等于当前人的身高。在枚举的过程中顺便就记录了f。
//这时候队列里存储前一个人的高度从0到j,队首就是最小的f。
f=dp[now^1][j]-c*j;
while(head<tail&&q[tail-1]>f) tail--;
q[tail++]=f,dp[now][j]=(j<h)?(oo):(q[head]+c*j+(j-h)*(j-h));
}head=tail=0;
for(int j=100;j>=0;j--){
//这时候队列里存储前一个人的高度从100到j,同上。
f=dp[now^1][j]+c*j;
while(head<tail&&q[tail-1]>f) tail--;
q[tail++]=f,dp[now][j]=(j<h)?(oo):(min(dp[now][j],q[head]-c*j+(j-h)*(j-h)));
}
}for(int i=0;i<=100;++i) ans=min(ans,dp[now][i]);
if(n) print(ans),putchar(10);
}
}
发现有一个特殊的地方: ,这里的 , 是依次增加的,于是我们只需要记录最小值就行了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
const int oo=1<<30;
int dp[2][maxn];
int n,c,now,h,f,g,ans;
inline void print(int x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int main(){
while(scanf("%d%d",&n,&c)==2){
ans=oo,now=0,scanf("%d",&h);
for(int i=0;i<h;++i) dp[now][i]=oo;
for(int i=h;i<=100;++i) dp[now][i]=(i-h)*(i-h);
for(int i=2;i<=n;++i){
scanf("%d",&h),f=oo,g=oo,now^=1;
for(int j=0;j<=100;++j)
f=min(dp[now^1][j]-c*j,f),dp[now][j]=(j<h)?(oo):(f+c*j+(j-h)*(j-h));
for(int j=100;j>=0;j--)
g=min(dp[now^1][j]+c*j,g),dp[now][j]=(j<h)?(oo):(min(dp[now][j],g-c*j+(j-h)*(j-h)));
}for(int i=0;i<=100;++i) ans=min(ans,dp[now][i]);
if(n) print(ans),putchar(10);
}
}
小结一下,以后看到转移方程类似 的,都可以考虑单调队列维护最值。这里的 为一个常数。看到绝对值勇敢拆开。不要怕分类讨论麻烦。