首先考虑最简朴的dp。
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i个人身高为
j
j
j的最小花费。显然这个值只与它前面一个人的身高有关。枚举前一个人身高
k
k
k,枚举当前人身高
j
j
j,则有转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
k
]
+
c
∗
a
b
s
(
j
−
k
)
+
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
dp[i][j]=dp[i-1][k]+c*abs(j-k)+(h[i]-j)*(h[i]-j)
dp[i][j]=dp[i−1][k]+c∗abs(j−k)+(h[i]−j)∗(h[i]−j)
时间复杂度
O
(
n
×
h
[
i
]
2
)
O(n\times h[i]^2)
O(n×h[i]2)
#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);
}
}
用单调队列优化。观察转移方程。
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
k
]
+
c
∗
a
b
s
(
j
−
k
)
+
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
dp[i][j]=dp[i-1][k]+c*abs(j-k)+(h[i]-j)*(h[i]-j)
dp[i][j]=dp[i−1][k]+c∗abs(j−k)+(h[i]−j)∗(h[i]−j)
发现有一个绝对值。分类讨论。如果
j
>
=
k
j>=k
j>=k
(
j
<
k
时
同
理
)
(j<k时同理)
(j<k时同理),那么原式可以化为:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
k
]
+
c
∗
j
−
c
∗
k
+
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
dp[i][j]=dp[i-1][k]+c*j-c*k+(h[i]-j)*(h[i]-j)
dp[i][j]=dp[i−1][k]+c∗j−c∗k+(h[i]−j)∗(h[i]−j)
发现当
i
i
i,
j
j
j确定的时候,
c
∗
j
c*j
c∗j和
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
(h[i]-j)*(h[i]-j)
(h[i]−j)∗(h[i]−j)是确定的。于是把原式分成两个部分。
d
p
[
i
]
[
j
]
=
(
d
p
[
i
−
1
]
[
k
]
−
c
∗
k
)
+
(
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
+
c
∗
j
)
dp[i][j]=(dp[i-1][k]-c*k)+((h[i]-j)*(h[i]-j)+c*j)
dp[i][j]=(dp[i−1][k]−c∗k)+((h[i]−j)∗(h[i]−j)+c∗j)
令:
f
[
i
]
[
k
]
=
d
p
[
i
]
[
k
]
−
c
∗
k
,
g
[
i
]
[
j
]
=
(
h
[
i
]
−
j
)
∗
(
h
[
i
]
−
j
)
+
c
∗
j
f[i][k]=dp[i][k]-c*k,g[i][j]=(h[i]-j)*(h[i]-j)+c*j
f[i][k]=dp[i][k]−c∗k,g[i][j]=(h[i]−j)∗(h[i]−j)+c∗j
于是
d
p
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
k
]
)
+
g
[
i
]
[
j
]
dp[i][j]=min(f[i-1][k])+g[i][j]
dp[i][j]=min(f[i−1][k])+g[i][j]。
f
f
f数组便可以用一个单调队列来维护。具体实现见代码。
另外,不要忘了身高不能低于原始身高,因为身高只可增不可降。。。
#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);
}
}
发现有一个特殊的地方: d p [ i ] [ j ] = m i n ( f [ i − 1 ] [ k ] ) + g [ i ] [ j ] ( j > = k ) dp[i][j]=min(f[i-1][k])+g[i][j](j>=k) dp[i][j]=min(f[i−1][k])+g[i][j](j>=k),这里的 k ∈ [ 1 , j ] k∈[1,j] k∈[1,j], j j j是依次增加的,于是我们只需要记录最小值就行了。
#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);
}
}
小结一下,以后看到转移方程类似 d p [ i ] = m i n / m a x ( f [ x ] ) + k dp[i]=min/max(f[x])+k dp[i]=min/max(f[x])+k的,都可以考虑单调队列维护最值。这里的 k k k为一个常数。看到绝对值勇敢拆开。不要怕分类讨论麻烦。
博客介绍了最简朴的dp,dp[i][j]表示第i个人身高为j的最小花费,其转移方程时间复杂度为O(n×h[i]2)。通过单调队列进行优化,对转移方程中的绝对值分类讨论,将原式拆分,用单调队列维护f数组。还提醒身高只可增不可降,最后总结类似转移方程可用单调队列维护最值。
182

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



