第一次尝试写动态规划(Dynamic Planning)=
问题如下:
-------------------------------------------------------------------------------------------------------------------------
最小代价子母树
设有一排数,共n个,例如:22 14 7 13 26 15 11.任意2个相邻的数可以进行归并,归并的代价为该两个数的和,经过不断的归并,最后归为一堆,而全部归并代价的和称为总代价,给出一种归并算法,使总代价为最小.
输入、输出数据格式与“石子合并”相同。
输入样例:
4
12 5 16 4
-------------------------------------------------------------------------------------------------------------------------
为了说明算法的过程,我们先分析一些简单的情况:
(1)n=2:
当n=2时,仅有1种堆法,因此总的归并代价为2堆沙子的和。
(2)n=3:
当n=3时,有2种堆法。
第1种堆法的总代价为20+24,第2种堆法的总代价为11+24。由此可见,最后一次的归并代价为全部沙子数量的和,对任何归并方案都是相同的,因此总的代价将取决于第一次归并,第1种方法的第1次归并代价为20,第2种方法的第1次归并代价为11,因此第种方法比第1种好。
(3)n=4:
方法a总代价:20+34+40=94 方法b总代价:21+34+40=95 方法c总代价:20+20+40=80 方法d总代价:21+27+40=88
方法e总代价:20+27+40=87
当n=4时,共有5种归并的方法,这5种方法可以分为3类:
1)包括a和b基本方法是先归并前面的3堆,在归并最后一堆,由于归并最后一堆的方法是相同的,所以在归并前3堆时不同的方法将产生不同的结果。a的总代价为54,b的总代价为55,所以第1类方法中a比b优;
2)仅有1种方法,c,分别归并2堆的代价分别为20,20,相加为40,共80;
3)包括d和e,基本方法是先归并后面3堆,再归并第1堆,由于归并第1堆的方法是相同的,所以在归并后3堆时不同的方法将产生不同的结果,由上面的分析可知e比d优;
由此我们可以发现,将每一层的最优解找出来,就可以用组合的方法列举出每两个解得到的解,因为每一个结点都是最优解,所以各个最优解组合出来的解也必定为最优解之一,再从中找出最小的一个就是该根结点所对应的沙堆的最小归并方案。
用二维数组表示为:
刚开始犯了很多错,
比如先开始是这么写的(一脸蒙蔽):
1 for (int i=1;i<=n;i++) 2 for (int j=i+1;j<=n;j++) 3 for (int k=i;k<=j-1;k++) 4 f[i][j]=(f[i][j]>f[i][k]+f[k+1][j]+g[i][j])?f[i][k]+f[k+1][j]+g[i][j]:f[i][j];//未考虑递推关系
手动挥手.jpg
正解如下:
1 #include "iostream" 2 #include "cstdio" 3 #include "queue" 4 #define qwq 21000000 5 int n,m,q; 6 using namespace std; 7 int f[1000][1000]; 8 int g[1000][1000]; 9 int gra[1000],minn=-2100000; 10 int main() 11 { 12 int n; 13 cin>>n; 14 for (int i=1;i<=n;i++) 15 cin>>gra[i]; 16 17 for (int i=1;i<=n;i++) 18 g[i][i]=gra[i]; 19 20 for (int i=1;i<=n;i++) 21 for (int j=i+1;j<=n;j++) 22 g[i][j]=g[i][j-1]+gra[j]; 23 24 for (int i=1;i<=n;i++) 25 for (int j=1;j<=n;j++) 26 f[i][j]=(i!=j)?qwq:0; //i -> j(i)的代价为 +∞(0) 27 28 for (int p=2;p<=n;p++)//穷举堆数//因为是递推(Recursion)所以从小堆数向大堆数 29 for (int i=1;i<=(n-p+1);i++)//一段的确定性(Certainty) 列举起点 30 { 31 int j=i+p-1; //终点(Destination) 32 for (int k=i;k<=j-1;k++)//穷举隔开位置 //注意超界所致的溢出(Overflow) 33 f[i][j]=(f[i][j]>f[i][k]+f[k+1][j]+g[i][j])?f[i][k]+f[k+1][j]+g[i][j]:f[i][j];//三目运算符不清楚的可以百度,下面也有简介 34 } //正确写出状态转移方程//无后效性 (Unfollow-up Effect) 35 cout<<f[1][n]<<endl; 36 }
动态规划真的是个神奇的东西,
它适合求解多阶段(状态转换)决策问题的最优解,也可用于含有线性或非线性递推关系的最优解问题。
但其实它并不全能,
1,最优化原理
2,无后效性
必须满足这两个条件才能用,
当然,
写动态规划最主要的是写出状态转移方程,
其次是边界条件。
附一:
三目运算符普遍的用处就是减少if语句的使用
例如:
1 i=(括号条件成立与否)?(成立):(不成立);