动态规划:
DP,动态规划是通过组合子问题的解来解决原问题;动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题;动态规划算法对每个子子问题只求解一次;动态规划通常用来求解最优化问题。
解题步骤:
- 判题题意是否为找出一个问题的最优解
- 将原问题分解为子问题
- 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程),如何从一个或多个已知状态求出另一个未知状态的值。(递推型)
- 讨论底层的边界问题,确定一些初始状态(边界状态)的值,并用数组存储初始子问题的解
- 解决问题(通常使用数组进行迭代求出最优解)
经典例题:
1.青蛙跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。
1、判题题意是否为找出一个问题的最优解
本质上dp算法是一种高效的枚举算法,只不过有时我们的问题是找出所有可能枚举值中满足某个最优条件的那个状态,所有很多没有经过系统的运筹学数学知识培养的同学会把dp纯粹当成了优化问题的算法。许多非优化问题,比如需要枚举出所有可能结果的问题,dp是非常适用的。
2、从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题
题目中没有给粟子,我们可以自己举点粟子。例如,跳上一个6级台阶台阶,有多少种跳法;由于青蛙一次可以跳两阶,也可以跳一阶,所以我们可以分成两个情况
(1)青蛙最后一次跳了两阶,问题变成了“跳上一个4级台阶台阶,有多少种跳法”
(2)青蛙最后一次跳了一阶,问题变成了“跳上一个5级台阶台阶,有多少种跳法”
由上可得f(6) = f(5) + f(4);
由此类推,f(4)=f(3) +f(2)
3.、从下往上分析问题 ,找出这些问题之间的关联(状态转移方程)
跟上面的例题一相同,可以由f(1)逐渐迭代上去
由2可得,状态转移方程为:f(n)=f(n-1)+f(n-2)
4、边界情况分析
跳一阶时,只有一种跳法,所以f(1)=1
跳两阶时,有两种跳法,直接跳2阶,两次每次跳1阶,所以f(2)=2
跳两阶以上可以分解成上面的情况
代码:
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int n=sc.nextInt();
//0,1,2是解题的边界
if(n<=0){
System.out.println(0);
}
if(n==1){
System.out.println(1);
}
if(n==2){
System.out.println(2);
}
if(n>=3){
int[] value=new int[n+1];
value[0]=0;
value[1]=1;
value[2]=2;
for(int i=3;i<=n;i++){
//核心代码,动态转移方程
value[i]=value[i-1]+value[i-2];
}
System.out.println(value[n]);
}
}
sc.close();
}
}
2.0-1背包问题
问题描述: 有n个物品(每种物品仅有一件),每种物品i都有自己的重量Wi和价值Vi。现有给定容量为M的背包,我们应该如何选择物品装入背包,使得装入背包的物品总价值最大?
问题分析:
k表示你面对的物品编号,即1~n,
c表示你面对k号物品时,背包的剩余容量
b(k,c)表示面对k号物品,并作出拿或不拿的选择之后,背包里面的物品总价值
第一种情况:
如果第k件物品的重量w[k]比此时的背包的剩余重量c大了,那我肯定是拿不动了,即w[k]>c。所以此时包中物品的价值就是我拿的前一个物品之后包中的价值,即 **b(k,c)=b(k-1,c).**包中剩余空间不变,还是c。
第二种情况:
如果我拿得动第k件物品,即第k件物品的重量w[k]<c,面对k号物品,无外乎两种选择,拿或者不拿,这时我就要根据拿走之后产生的效益进行决策了:
(1)不拿k号物品,那么此时包中物品的总价值b(k,c)=b(k-1,c),和第一种拿不动k号物品的一样。
(2)拿走k号物品,那么此时包中物品的总价值b(k,c)=b(k-1,c-w[k])+v[k]拿了第k件物品后,那我的包中的价值肯定就是原先的价值再加上第k件物品的价值(动态规划),而且拿了之后包中的剩余容量就为c-w[k]了。
**总结一下,就是如下的公式了:b(k,c)=max{b(k-1,c),b(k-1,c-w[k])+v[k]},即剩下的只需要比较这两种方式谁的效益大即可。**
代码如下:
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
//背包容量
int sum=sc.nextInt();
//物品个数
int n=sc.nextInt();
//每个物品的重量
int[] w=new int[n];
//每个物品的价值
int[] v=new int[n];
//var[m][n]表示面对第m个物品,作出拿或者不拿的选择之后,背包里的总价值
int[][] var=new int[n][sum];
for(int i=0;i<n;i++){
w[i]=sc.nextInt();
}
for(int i=0;i<n;i++){
v[i]=sc.nextInt();
}
for(int i=1;i<n;i++){
for(int j=1;j<sum;j++){
if(w[i]>j){
var[i][j]=var[i-1][j];
}
else{
var[i][j]=Math.max(var[i-1][j],var[i-1][j-w[i]]+v[i]);
}
}
}
System.out.println(var[n-1][sum-1]);
}
sc.close();
}
}