题意: 给定一个长度为N的数组,我们可以对其中的数进行修改,每次修改有一个代价,对于一个数x,修改成y之后的代价就是abs(x-y)。
现在我们要将这个数列变成一个单调不减的,问所需要的代价。
其中N<=5000,-10^9=<a[i]<=10^9
解决
solution 1
看到这个题目,很容易就想到了DP,然后有了一个非常暴力的方程。
用f[i][j]代表前i个数,最后一个数的高度为j所需要的最少代价。
很容易写出方程f[i][j]=Min(f[i-1][k]+abs(j-k))
这个方程时间复杂度为O(N*max*max),其中max是最大的a[i],当然,对于负数的情况可以将整个数组向上平移变成正数。
可以看到的是,这个方程的时间复杂度实在太高了,在空间上也难以承受。所以我们只能另寻他法。
solution 2
有了上面的基础,很快有了优化的办法,那就是离散化!
既然总的数据量只有5000,那么我们可以把每个数在数列中的rank作为这个数的大小
那么得到f[i][j]代表前i个数,最后一个数为数列中第j大的数的代价。
方程可以写出f[i][j]=Min(f[i-1][k]+abs(b[j]-a[i])),其中b[j]就是第j大数的大小。
时间复杂度o(N^3),还是不能很好的解决问题,所以我们还要寻求其他的办法。
solution 3
然后我开始想这样一个问题,如果说f[i][j]要大于f[i][j-1],那么计算这样的f[i][j]还有什么意义呢?
因为我第i个数得到的大小为b[j-1]的消耗要比得到b[j]的消耗少,(b数列是单增的),那么在i+1个数的决策的时候,这样的
f[i][j]还有什么意义呢?!(大家可以自己证明一下)
所以我们必须还要压缩状态!
修改我们的方程,用f[i][j],代表前i个数,第i个数小于等于b[j]的最小花费
方程可以写成f[i][j]=Min(f[i][j-1],f[i-1][j]+abs(a[i]-b[j]))
然后由于转移只是用到了相邻状态,可以用滚动数组来优化空间
即 f[i]=Min(f[i-1],f[i]+abs(a[i]-b[j]))
时间复杂度 O(N^2),空间复杂度O(N)
这个问题还有更优秀的算法,在此不再做过多讲解,这里已经能很好的解决这个问题,我们可以看到整个思维的过程其实是循序渐进的。
题目第一次由于对数的大小估计不足最终RE了,一定要使用long long
这里是本人的最终代码,大神们不要BS
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <math.h>
4 #define N 5050
5 long long dp[ N ] , a [ N ] , b [ N ] ,ans ;
6 int cmp (const void * a , const void * b )
7 {
8 return *(int *) a - * ( int * ) b ;
9 }
10 long long Min(long long x, long long y)
11 {
12 return x>y?y:x;
13 }
14 int main ( void )
15 {
16 int i , n , j ;
17 scanf("%d",&n);
18 for ( i = 1 ; i <= n ; i++ )
19 {
20 scanf( "%d" , & a [ i ] ) ;
21 b[i]=a[i];
22 }
23 qsort (b+1,n,sizeof(b[0]),cmp);
24
25 for ( i = 1 ; i <= n ; i++ )
26 for ( j = 1 ; j <= n ; j++ )
27 {
28 dp[j]+=abs(b[j]-a[i]);
29 if (j>1)
30 dp[j]=Min(dp[j],dp[j-1]);
31 }
32 ans = dp[1] ;
33 for (i=2;i<=n;i++)
34 if (dp[i]<ans)
35 ans=dp[i];
36 printf("%I64d\n",ans);
37 return 0 ;
38 }