dp:dynamic programming
记忆化搜索与动态规划
01背包
package 程序设计竞赛;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class dp$背包问题 {
private static int n;
private static int[] w;
private static int[] v;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
n = sc.nextInt();//数量
w = new int[n];
v = new int[n];
for(int i=0;i<n;i++){
w[i]=sc.nextInt();
v[i]=sc.nextInt();
}
int W=sc.nextInt();//总重量
System.out.println(rec(0,W));
}
public static int rec(int i,int j){
int res;
if(i==n){
//已经没有剩余的物品了
res=0;
}else if(j<w[i]){
//总重量小于加上去的总量,即无法挑选这个物品
res=rec(i+1,j);
}else{
//挑选和不挑选的两种情况都尝试一下
res=Math.max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
}
return res;//此情况的价值
}
}
package 程序设计竞赛;
import java.util.Scanner;
public class dp¥背包记忆数组 {
private static int n;
private static int[] w;
private static int[] v;
private static int[][] dp;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
n = sc.nextInt();//数量
w = new int[n];
v = new int[n];
dp= new int[n][n];
//初始化
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
dp[i][j]=-1;
}
for(int i=0;i<n;i++){
w[i]=sc.nextInt();
v[i]=sc.nextInt();
}
int W=sc.nextInt();//总重量
System.out.println(rec(0,W));
}
public static int rec(int i,int j){
if(dp[i][j]>=0){
//已经计算过的话直接使用之前的结果
return dp[i][j];
}
int res;
if(i==n){
//已经没有剩余的物品了
res=0;
}else if(j<w[i]){
//总重量小于加上去的总量,即无法挑选这个物品
res=rec(i+1,j);
}else{
//挑选和不挑选的两种情况都尝试一下
res=Math.max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
}
return dp[i][j]=res;//此情况的价值
}
}
这微小的改进能降低多少复杂度呢?
对于同样的参数,只会在第一次调用的是偶执行递归部分,第二次之后就直接返回。参数的组合不过nW种,而函数内只调用2次递归,所以只需要O(NW)的复杂度就能解决这个问题。只需略微改良,可解的问题的规模就可以大幅提高。这种方法一般称为记忆化搜索
C++中使用memset初始化,而Java中没有
暴力枚举的写法
package 程序设计竞赛;
import java.util.Scanner;
public class dp¥背包二重循环 {
private static int n;
private static int[] w;
private static int[] v;
private static int[][] dp;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
n = sc.nextInt();//数量
w = new int[n];
v = new int[n];
dp= new int[n][n];
//初始化
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
dp[i][j]=-1;
}
for(int i=0;i<n;i++){
w[i]=sc.nextInt();
v[i]=sc.nextInt();
}
int W=sc.nextInt();//总重量
for(int i=n-1;i>=0;i--){
for(int j=0;j<=W;j++){
if(j<w[i]){//不可以加的
dp[i][j]=dp[i+1][j];
}else{
dp[i][j]=Math.max(dp[i+1][j],dp[i+1][j-w[i]+v[i]]);
}
}
}
System.out.println(dp[0][W]);
}
}
这个算法的复杂度与前面相同,也是O(nW),但是简洁了很多。以这种方式一步步按顺序求出问题的解的方法被称作动态规划法,dp.解决问题时既可以按照如上方式从记忆化搜索出发推到出递推式,熟练后也可以直接得出递推式
注意不要忘记初始化
因为全局数据的内容被初始化为0,所以前面的源代码中没有显式将初项=0进行赋值,不过当一次运行要处理多组输入数据时,必须要进行初始化,这点一定要注意。
各种各样的DP
最长公共子序列问题
还要注意数组的范围
package 程序设计竞赛;
import java.util.Scanner;
public class DP¥最长公共子序列问题 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
String sa=sc.next();
String sb=sc.next();
char[] a=sa.toCharArray();
char[] b=sb.toCharArray();
int[][] dp=new int[n+1][m+1];
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
dp[i][j]=0;
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
//注意这个,我之前写成了两个i,所以半天都是错误的
if(a[i]==b[j]){
dp[i+1][j+1]=dp[i][j]+1;
}else{
dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j]);
}
}
}
System.out.println(dp[n][m]);
}
}
完全背包问题
与01背包进行对比
这样书写,二者的差异就只变成循环的方向问题。
DP数组的再利用
01背包问题之2
这一问题与最初的01背包问题相比,知识修改了限制条件的大小。
多重部分和问题
package 程序设计竞赛;
import java.util.Scanner;
public class 多重部分和问题 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[] a=new int[n];
int[] b=new int[n];
//有n中不同的数字
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
//每种数字有几种
for(int i=0;i<n;i++){
b[i]=sc.nextInt();
}
int k=sc.nextInt();//目标和
boolean[][] dp=new boolean[n+1][k+1];
dp[0][0]=true;
for(int i=0;i<n;i++){
for(int j=0;j<=k;j++){
for(int d=0;d<=b[i]&&d*a[i]<=j;d++)
dp[i+1][j] |=dp[i][j-d*a[i]];
}
}
if(dp[n][k])
System.out.println("yes");
else
System.out.println("no");
}
}
package 程序设计竞赛;
import java.util.Scanner;
public class DP¥多重部门和问题 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[] a=new int[n];
int[] b=new int[n];
//有n中不同的数字
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
//每种数字有几种
for(int i=0;i<n;i++){
b[i]=sc.nextInt();
}
int k=sc.nextInt();//目标和
int[] dp=new int[k+1];
for(int i=0;i<k;i++)
dp[i]=-1;
dp[0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=k;j++){
if(dp[j]>=0)
dp[j]=b[i];
else if(j<a[i]||dp[j-a[i]]<=0){
dp[j]=-1;
}else
dp[j]=dp[j-a[i]]-1;
}
}
if(dp[k]>=0)
System.out.println("yes");
else
System.out.println("no");
}
}