记录结果再利用的“动态规划”(0,1背包)(完全背包)(lcs最长公共子序列问题)(多重部分和问题)

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");
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向上Claire

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值