uestc594 我要长高

传送门

首先考虑最简朴的dp。
d p [ i ] [ j ] dp[i][j] 表示第 i i 个人身高为 j j 的最小花费。显然这个值只与它前面一个人的身高有关。枚举前一个人身高 k k ,枚举当前人身高 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)
时间复杂度 O ( n × h [ i ] 2 ) O(n\times 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)
发现有一个绝对值。分类讨论。如果 j &gt; = k j&gt;=k ( j &lt; k ) (j&lt;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)
发现当 i i j j 确定的时候, c j c*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)
令:
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
于是 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]
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 &gt; = k ) dp[i][j]=min(f[i-1][k])+g[i][j](j&gt;=k) ,这里的 k [ 1 , j ] k∈[1,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 的,都可以考虑单调队列维护最值。这里的 k k 为一个常数。看到绝对值勇敢拆开。不要怕分类讨论麻烦。

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/88751089