能采用动态规划求解的问题的一般要具有3个性质:
(1) 最优子结构:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:有两层含义,第一层含义是,在推导后面阶段状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
本题题意是,将一个无序的序列中的每个数进行加或减,整理为不上升或不下降序列,求所付出的最小代价。假设将序列整理为不下降序列,则思路为:
1,确定子问题,dp[i][j]表示前i个数的序列中,最大值(可理解为第i个值)为j的最小代价。则原问题为min(k){dp[n][k]},求前n个数中最大值为1~m的最小代价。
2,列出状态转移公式dp[i][j]=abs(j-w[i])+min(dp[i-1][k]);(k<=j)。
3,进行一些初始化工作。
这样分析,问题是满足以上三个条件的。动态规划可行。
优化方面,
1,序列最大值可达1e9,而序列长度仅几千,因此可以采取离散化来避免M和T。这里的离散化很简单,等于将序列排序。复杂的离散化请参考往期博文,这道题用不到。
2,min(dp[i-1][k])的确定无须在每个循环都遍历一遍dp[i-1]。只需在每个循环令mn=Math.min(mn, dp2[i-1][j]);这样,时间复杂度由O(nnm)下降为O(n*n)
ac代码
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
class Main{
public static void main(String args[]) throws IOException {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int a[]=new int[n],b[]=new int[n];
int m=-1;
for(int i=0;i<n;i++) {
a[i]=sc.nextInt();
b[i]=a[i];
}
Arrays.sort(b);
int dp1[][]=new int[n][n];
for(int i=0;i<n;i++) {
int mn=Integer.MAX_VALUE;
if(i==0)mn=0;
for(int j=0;j<n;j++) {
if(i-1>=0) {
mn=Math.min(mn, dp1[i-1][j]);
}
dp1[i][j]=mn+Math.abs(b[j]-a[i]);
}
}
int ans=Integer.MAX_VALUE;
for(int i=0;i<n;i++) {
ans=Math.min(dp1[n-1][i], ans);
}
int dp2[][]=new int[n][n];
for(int i=0;i<n;i++) {
int mn=Integer.MAX_VALUE;
if(i==0)mn=0;
for(int j=n-1;j>=0;j--) {
if(i-1>=0) {
mn=Math.min(mn, dp2[i-1][j]);
}
dp2[i][j]=mn+Math.abs(b[j]-a[i]);
}
}
int ans2=Integer.MAX_VALUE;
for(int i=0;i<n;i++) {
ans2=Math.min(dp2[n-1][i], ans2);
}
//System.out.println(ans+" "+ans2);
System.out.println(Math.min(ans, ans2));
}
}