状态 dp[i][j] 表示 第i根柱子,高度为j的最小花费
转移方程
dp( i , j ) = Min { dp( i-1, k ) + | k - j | * C + ( j-a[i] )*( j-a[i]) }
如果我们 枚举 j 和 k, 因为 都小于 100, 时间复杂度也有 O( 10^9 ), 1000 ms也不够.
我们可以把绝对值拆分开来:
当 j >= k 时:
dp( i, j ) = Min{ dp( i-1, k ) - k*C + j*c + ( j-a[i])^2 }
当 j <= k 时:
dp( i, j ) = Min{ dp( i-1, k ) + k*C + j*C + (j-a[i])^2 }
可以观察到对于 j 而言, 只有
dp( i-1, k ) - k*C
dp( i-1, k ) + k*C
是变化的. 而后面部分相对 每一个不同J的固定的.
所以我们可以设定 函数 F,G :
G( j ) = Min { dp( i-1, k ) + k*C } 其中k 属于 [ j, 100 ] 区间
F( j ) = Min { dp( i-1, k ) - k*C } 其中k 属于 [ a[i-1], j ] 区间
所以最终结果为
dp ( i, j ) = Min{ G(j) - j*C , F(j) + j*C } + ( j-a[i] )^2
注意 因为 J 属于 区间 [ a[i-1], MaxHigh ]区间, 当J < a[i-1] 时要特殊处理
对于dp状态表示,因为我们只要相邻的两组状态,所以可以用滚动数组来优化空间
#include<cstdio> #include<cstdlib> #include<cstring> #define MIN(a,b) (a)<(b)?(a):(b) #define MAX(a,b) (a)>(b)?(a):(b) const int N = 100100; const int inf = 0x3fffffff; int a[N], dp[2][200], f[200], g[200]; int n, c, m; int main() { while( scanf("%d%d", &n,&c) != EOF) { m = 0; for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); m = MAX( m, a[i] ); } int cur = 0; for(int h = 0; h <= m; h++) if( h >= a[1] ) dp[cur][h] = (h-a[1])*(h-a[1]); else dp[cur][h] = inf; for(int i = 2; i <= n; i++) { for(int h = 0; h <= m+1; h++) { f[h]=g[h] = inf; dp[!cur][h] = inf; } // 函数f(k) 保存 区间 [ k, m ] 之间 MIN { dp[i-1,j] + j*c } for(int h = m; h >= a[i-1]; h--) f[h] = MIN( f[h+1], dp[cur][h]+h*c ); // 函数g(k) 保存 区间 [ a[i-1], k ] 之间 MIN { dp[i-1,j] - j*c } for(int h = a[i-1]; h <= m; h++) g[h] = MIN( g[h-1], dp[cur][h]-h*c ); for(int h = a[i]; h <= m; h++) { if( h < a[i-1] ) dp[!cur][h] = MIN( dp[!cur][h], f[a[i-1]] - h*c + (a[i]-h)*(a[i]-h) ); else dp[!cur][h] = MIN( dp[!cur][h] , MIN( f[h]-h*c , g[h]+h*c ) + (a[i]-h)*(a[i]-h) ); } cur = !cur; } int ans = inf; for(int h = a[n]; h <= m; h++) ans = MIN( ans, dp[cur][h] ); printf("%d\n", ans ); } return 0; }