比较容易理解的动态规划:http://blog.csdn.net/woshioosm/article/details/7438834
关于金子问题的部分选段:
子问题:
国王需要根据两个大臣的答案以及第9座金矿的信息才能判断出最多能够开采出多少金子。为了解决自己面临的问题,他需要给别人制造另外两个问题,这两个问题就是子问题。
思考动态规划的第一点—-最优子结构:
国王相信,只要他的两个大臣能够回答出正确的答案(对于考虑能够开采出的金子数,最多的也就是最优的同时也就是正确的),再加上他的聪明的判断就一定能得到最终的正确答案。我们把这种子问题最优时母问题通过优化选择后一定最优的情况叫做“最优子结构”。
思考动态规划的第二点—-子问题重叠:
实际上国王也好,大臣也好,所有人面对的都是同样的问题,即给你一定数量的人,给你一定数量的金矿,让你求出能够开采出来的最多金子数。我们把这种母问题与子问题本质上是同一个问题的情况称为“子问题重叠”。然而问题中出现的不同点往往就是被子问题之间传递的参数,比如这里的人数和金矿数。
思考动态规划的第三点—-边界:
想想如果不存在前面我们提到的那些底层劳动者的话这个问题能解决吗?永远都不可能!我们把这种子问题在一定时候就不再需要提出子子问题的情况叫做边界,没有边界就会出现死循环。
思考动态规划的第四点—-子问题独立:
要知道,当国王的两个大臣在思考他们自己的问题时他们是不会关心对方是如何计算怎样开采金矿的,因为他们知道,国王只会选择两个人中的一个作为最后方案,另一个人的方案并不会得到实施,因此一个人的决定对另一个人的决定是没有影响的。我们把这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做“子问题独立”。
这就是动态规划,具有“最优子结构”、“子问题重叠”、“边界”和“子问题独立”,当你发现你正在思考的问题具备这四个性质的话,那么恭喜你,你基本上已经找到了动态规划的方法。
有了上面的这几点,我们就可以写出动态规划的转移方程式,现在我们来写出对应这个问题的方程式,如果用gold[mineNum]表示第mineNum个金矿能够挖出的金子数,用peopleNeeded[mineNum]表示挖第mineNum个金矿需要的人数,用函数f(people,mineNum)表示当有people个人和编号为0、1、2、3、……、mineNum的金矿时能够得到的最大金子数的话,f(people,mineNum)等于什么呢?或者说f(people,mineNum)的转移方程是怎样的呢?
当mineNum = 0且people >= peopleNeeded[mineNum]时
f(people,mineNum) = gold[mineNum];
当mineNum = 0且people < peopleNeeded[mineNum]时
f(people,mineNum) = 0
当mineNum != 0时
f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1)中的较大者。
前两个式子对应动态规划的“边界”,后一个式子对应动态规划的“最优子结构”
合唱团:
题目描述
有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。
动态规划问题步骤如下:
1、构造问题所对应的过程。(假设f[key][k]表示选中的第k个数在key这个位置上,此时它的值就是最终解。要想知道这个值的大小,要求得f[key][k-1]的大小)
2、思考过程的最后一个步骤,看看有哪些选择情况。
(这里两种情况,负负得正,正正得正相比较得到最大的乘积)
3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。
(间隔为d,遍历key前面d个数的值,相乘得到最大乘积)
4、使得子问题符合“最优子结构”。
(得到k-1的最优解后,再次遍历n个数据,通过一样的过程获得k的最优解)
5、找到边界,考虑边界的各种处理方式。
(当k=1时,其实就是获得输入数据的最大值)
6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。
7、考虑如何做备忘录。(将每个位置的最大值都存于数组f[i][j],g[i][j])
8、分析所需时间是否满足要求。
9、写出转移方程式。
当k=1; f[key][1] = (long) a[key];
当k!=1; max1 = Math.max(f[left][k-1]*a[key], g[left][k-1]*a[key]);
代码如下:
package test;
import java.util.Scanner;
/**
* @author heye
* @date 2017年11月28日 下午5:50:31
* @version 合唱团 */
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int [] a= new int[n+1];
int [] max= new int[n+1];
for (int i=1;i<=n;i++){
a[i] = scanner.nextInt();
}
int kk = scanner.nextInt();
int dd = scanner.nextInt();
Long[][] f = new Long[n+1][n+1];//正
Long[][] g = new Long[n+1][n+1];//负
//防止空指针,全部初始化为0
for(int i=0;i<=n;i++) {
for(int j=0;j<=n;j++) {
f[i][j]=0L;
g[i][j]=0L;}
}
for(int key = 1;key <=n;key++) {
f[key][1] = (long) a[key];//表示当取一个数的情况,将输入数作为第一行
g[key][1] = (long) a[key];
}
//取几个,就有几列
for(int k=2;k<=kk;k++) {
//遍历key值
for (int key = k;key<=n;key++) {
Long max1 = (long) Integer.MIN_VALUE;
Long min1 = (long) Integer.MAX_VALUE;
//比较left的取值中,得到最大的
for(int left = Math.max(k-1, key-dd);left<=key-1;left++) {
if(max1<Math.max(f[left][k-1]*a[key], g[left][k-1]*a[key]))
max1 = Math.max(f[left][k-1]*a[key], g[left][k-1]*a[key]);
//记录负数为了防止丢弃了负负得正的数
if(min1>Math.min(f[left][k-1]*a[key], g[left][k-1]*a[key]))
min1 = Math.min(f[left][k-1]*a[key], g[left][k-1]*a[key]);
}
//记录该位置的最大数和最小数
f[key][k]=max1;
g[key][k]=min1;
}
}
Long result = f[1][kk];
//遍历kk列中最大的值
for(int i=1;i<=n;i++) {
if(result<f[i][kk])
result = f[i][kk];
}
System.out.println(result);
}
}