蓝桥杯每日一题------背包问题(二)

news2024/12/26 0:03:00

前言

本次讲解背包问题的一些延申问题,新的知识点主要涉及到二进制优化,单调队列优化DP,树形DP等。

多重背包

原始做法

多重背包的题意处在01背包和完全背包之间,因为对于每一个物品它规定了可选的个数,那么可以考虑将完全背包的第三维修改一下,j2表示选择的当前物品的个数,给它限制为s[i]。代码如下所示,

import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int k = scanner.nextInt();
		int[] v = new int[n + 1];
		int[] w = new int[n + 1];
		int[] s = new int[n + 1];
		for (int i = 1; i <= n; i++) {
			v[i] = scanner.nextInt();
			w[i] = scanner.nextInt();
			s[i] = scanner.nextInt();
		}
		int[][] dp = new int[n + 1][k + 1];
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j < k + 1; j++) {
				for (int j2 = 0; j2 * v[i] <= j && j2 <= s[i]; j2++) {
					dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - j2 * v[i]] + j2 * w[i]);
//					dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - j2 * v[i]] + j2 * w[i]);//原本写的j2 = 1开始
				}
			}
		}
		System.out.println(dp[n][k]);
	}
}

这个的时间复杂度是 O ( N ∗ V ∗ S ) = 1000 ∗ 20000 ∗ 20000 = 4 e 11 O(N*V*S)=1000*20000*20000=4e11 ONVS=10002000020000=4e11,时间复杂度太高需要优化。

二进制优化做法

假设最终的选择方案中,第i个物品需要选择p次,按照原始做法,这p次需要遍历p遍才能选出来,那么还有没有其它更快的做法?如果大家学过倍增或者快速幂学的较为深入的话,这里其实就是考虑快速算出数字p,我们很容易想到二进制,二进制可以表示十进制里的任何一个数字。比如p=10,写成二进制是1010,如果我们预处理过每个物品二进制的值,只需要执行4次就可以了,第一次遍历2的3次方,要选择。第二次遍历到2的2次方,不选择。第三次遍历2的1次方,要选择。第四次遍历2的0次方,不选择。
怎么预处理呢?假设s[i]是10,那么把2进制每一位表示的数字都处理出来,让它单独表示一个物品,比如1个物品i(s[i]个还剩9个),2个物品i(s[i]个还剩7个),4个物品i(s[i]个还剩3个),8个物品i(不够8个了,直接剩余的3个作为1组就行。这里为啥不能用8个?如果用了八个,在后续选择的过程中,我选了4个物品i和8个物品i这样就一共选了13个物品i,超出了它的最大可选值)。
预处理后我要怎么用呢?每一个看作单独的一个新物品,那么一个可选个数为S个的物品,可以被处理处logS(以2为底的S的对数)个新物品,那么S的最大值为20000,lon(20000)≈15。现在就变成了每个物品只能选择一个,可以使用01背包的代码,时间复杂度为 O ( N ∗ l o g S ∗ V ) = 1000 ∗ 15 ∗ 20000 = 3 e 8 O(N*logS*V)=1000*15*20000=3e8 ONlogSV=10001520000=3e8。时间复杂度比一开始好了很多,但是对于本题的数据规模还是不能通过。完整代码如下,


import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
	static class good{
		int v,w;
		public good(int v, int w) {
			super();
			this.v = v;
			this.w = w;
		}
	}
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	int n = scanner.nextInt();
	int V = scanner.nextInt();
	int[] v = new int[n+1];
	int[] w = new int[n+1];
	int[] s = new int[n+1];
	for (int i = 1; i < w.length; i++) {
		v[i] = scanner.nextInt();
		w[i] = scanner.nextInt();
		s[i] = scanner.nextInt();
	}
	//二进制预处理每一个物品
	List<good> list = new ArrayList<good>();
	list.add(new good(0,0));
	for (int i = 1; i < n+1; i++) {
		for (int j = 1; j <= s[i]; j *= 2) {
			list.add(new good(v[i]*j, w[i]*j));
			s[i] -= j;
		}
		if(s[i]>0) {
			list.add(new good(v[i]*s[i], w[i]*s[i]));
		}
	}
	//01背包代码
	int[][] dp = new int[list.size()][V+1];
	for (int i = 1; i < dp.length; i++) {
		for (int j = 0; j < V+1; j++) {
			dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);
			if(j>=list.get(i).v)
				dp[i][j] = Math.max(dp[i][j], dp[i-1][j-list.get(i).v]+list.get(i).w);
		}
	}
	System.out.println(dp[list.size()-1][V]);
}
}

单调队列优化做法

这个如果光看别人推导还是无法完全理解其中含义,还是需要自己进行推导。我们现在就开始进行推导,算法的本质其实和数学是有一定联系的,如果你光想百思不得其解,那么这个时候就该动笔了,动笔有两个方向,一个是自己推导,一个是根据它的推导结果进行模拟。
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] , f [ i − 1 ] [ j − 2 ∗ v [ i ] ] + 2 ∗ w [ i ] , f [ i − 1 ] [ j − 3 ∗ v [ i ] ] + 3 ∗ w [ i ] , f [ i − 1 ] [ j − 4 ∗ v [ i ] ] + 4 ∗ w [ i ] ) + . . . , f [ i − 1 ] [ j − t ∗ v [ i ] ] + t ∗ w [ i ] f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i],f[i-1][j-3*v[i]]+3*w[i],f[i-1][j-4*v[i]]+4*w[i])+...,f[i-1][j-t*v[i]]+t*w[i] f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i],f[i1][j2v[i]]+2w[i],f[i1][j3v[i]]+3w[i],f[i1][j4v[i]]+4w[i])+...,f[i1][jtv[i]]+tw[i]
当i固定时,其中 j − t ∗ v [ i ] < v [ i ] j-t*v[i]<v[i] jtv[i]<v[i],也就是 r = j − t ∗ v [ i ] r=j-t*v[i] r=jtv[i]是j关于v[i]的余数。r可能的取值为 [ 0 , v [ i ] − 1 ] [0,v[i]-1] [0,v[i]1],对于r来说,我们可以有下述公式,下述公式是上述公式倒推的结果。
f [ i ] [ r ] = f [ i − 1 ] [ r ] f[i][r]=f[i-1][r] f[i][r]=f[i1][r]
f [ i ] [ r + v [ i ] ] = m a x ( f [ i − 1 ] [ r + v [ i ] ] , f [ i − 1 ] [ r ] ) f[i][r+v[i]]=max(f[i-1][r+v[i]],f[i-1][r]) f[i][r+v[i]]=max(f[i1][r+v[i]],f[i1][r])
f [ i ] [ r + 2 ∗ v [ i ] ] = m a x ( f [ i − 1 ] [ r + 2 ∗ v [ i ] , f [ i − 1 ] [ r + v [ i ] ] , f [ i − 1 ] [ r ] ) f[i][r+2*v[i]]=max(f[i-1][r+2*v[i],f[i-1][r+v[i]],f[i-1][r]) f[i][r+2v[i]]=max(f[i1][r+2v[i],f[i1][r+v[i]],f[i1][r])

f [ i ] [ r + s [ i ] ∗ v [ i ] ] = m a x ( f [ i − 1 ] [ r + s [ i ] ∗ v [ i ] ] , f [ i − 1 ] [ r + ( s [ i ] − 1 ) ∗ v [ i ] ] . . . f [ i − 1 ] [ r ] ) f[i][r+s[i]*v[i]]=max(f[i-1][r+s[i]*v[i]],f[i-1][r+(s[i]-1)*v[i]]...f[i-1][r]) f[i][r+s[i]v[i]]=max(f[i1][r+s[i]v[i]],f[i1][r+(s[i]1)v[i]]...f[i1][r])
f [ i ] [ r + ( s [ i ] + 1 ) ∗ v [ i ] ] = m a x ( f [ i − 1 ] [ r + ( s [ i ] + 1 ) ∗ v [ i ] ] , f [ i − 1 ] [ r + s [ i ] ∗ v [ i ] ] . . . f [ i − 1 ] [ r + v [ i ] ] ) f[i][r+(s[i]+1)*v[i]]=max(f[i-1][r+(s[i]+1)*v[i]],f[i-1][r+s[i]*v[i]]...f[i-1][r+v[i]]) f[i][r+(s[i]+1)v[i]]=max(f[i1][r+(s[i]+1)v[i]],f[i1][r+s[i]v[i]]...f[i1][r+v[i]])
对于第i个物品,最多只能选择s[i]个,一开始的体积是(s[i]+1)*v[i],体积变成r+v[i]时,已经选了s[i]个物品,后续不能再选择了。如果学过滑动窗口其实可以理解成窗口的大小为s[i]+1,这里的+1来源于不选择第i个物品,其余是依次选择1个,2个…s[i]个物品。
再接着向后写,最后一个是
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] . . . f [ i − 1 ] [ j − s [ i ] ∗ v [ i ] ] ) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]...f[i-1][j-s[i]*v[i]]) f[i][j]=max(f[i1][j],f[i1][jv[i]]...f[i1][js[i]v[i]])
f[i][j]等于对大小为s[i]+1的窗口取最大值,用单调队列去维护,这里只需要O(1)的时间复杂度,那么对于第1维度当前考虑前i个物品,不能省略,和以前一样。对于第2个维度,体积,这里只需要遍历j对于v[i]的余数r即可,然后再来一个for循环,遍历当前的体积,但是他是在r的基础上累加v[i]直到大于总体积j,那么虽然这里是两个for循环遍历的体积,但是总的次数依然是从体积的次数,只不过改变了遍历的顺序,先遍历对v[i]取模余0的体积大小,再遍历对v[i]取模余1的体积大小,依次类推。看一下这里的代码,

for(int i = 1;i <= N;i++) {
		for(int r = 0;r < v[i];r++) {
			for(int j = r;j <= M;j += v[i]) {
			}
		}
	}

所以这个思路的时间复杂度是 O ( N ∗ V ) = 1000 ∗ 20000 = 2 e 7 O(N*V)=1000*20000=2e7 O(NV)=100020000=2e7,此时的时间复杂度就符合要求。本题的关键在于理解上述for循环,理解这里后面就是顺理成章的事情。
接下来考虑单调队列实现滑动窗口。这里简单介绍一下滑动窗口,假设有一个长度为10的数组,滑动窗口的大小是3,求滑动窗口里的最大值,其过程如下,
从数组下标为1初开始遍历,依次向队列里面添加元素。
第一个while循环把此时不在窗口内的数字从队列里面删除。
假设此时在队列里面的数组下标分别为1,2,3,4。队尾元素减队头元素+1就是此时队列里的元素个数(注意这里存的是数组对应的下标),如果大于3,需要将队头元素移除(因为对头元素是最早进入队列的,比如这个例子里就要把数字1移除)。重复上述过程,直到队列里面的元素个数等于3。
但是对于本题而言,所谓的下标表示的是此时的体积,只要体积之差不超过s[i]*v[i]即可,注意这里的灵活变通,代码如下,

while(!queue.isEmpty()&&j-queue.peekFirst()>s[i]*v[i]) queue.pollFirst();

第二个while循环把后续不会对答案构成贡献的数字提前删掉。当前维护的是窗口里的最大值,那么对于队列而言,队头元素维护的就是最大值,整个队列从队头到队尾是单调递减序列。那么如果新加入的元素是要放在队尾的,如果它比此时队尾的元素大,就要删除队尾的元素,重复执行,直到它比队尾的元素小,或者队列为空。(原因:假设队列里此时有2,3,4,此时要加入5,a[5]>a[4],也就是说如果窗口内同时存在a[4]和a[5],a[5]会是最大值,也就是只要有a[5]存在a[4]永远不会成为最大值,而随着窗口的移动,a[4]会先被移走,所以只要a[4]在,a[5]一定在,那么a[4]不会对答案产生贡献,直接删除不会造成影响)
注意对于本题而言,对应的值应该加上这几个物品的价值,代码如下,

while(!queue.isEmpty()&&dp[i-1][queue.peekLast()]+(j-queue.peekLast())/v[i]*w[i]<=dp[i-1][j]) queue.pollLast();

( j − q u e u e . p e e k L a s t ( ) ) / v [ i ] (j-queue.peekLast())/v[i] (jqueue.peekLast())/v[i]表示对于体积为j时,选择的第i个物品的个数。
while结束后,把当前值插入队列,这里插入的是dp[i-1][j],但是队列里面只记录j,所以插入的是j,然后把队列的最大值赋值给dp[i][j]。代码如下,

queue.add(j);
dp[i][j] = dp[i-1][queue.peekFirst()]+(j-queue.peekFirst())/v[i]*w[i];

注意对于每一个物品队列是不同的,所以要有清空队列的操作,完整代码如下,

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;

public class Main{
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	int N = scanner.nextInt();
	int M = scanner.nextInt();
	int v[] = new int[N+1];
	int w[] = new int[N+1];
	int s[] = new int[N+1];
	for(int i = 1;i <= N;i++) {
		v[i] = scanner.nextInt();
		w[i] = scanner.nextInt();
		s[i] = scanner.nextInt();
	}
	int dp[][] = new int[N+1][M+1];
	ArrayDeque<Integer> queue = new ArrayDeque<Integer>();
	for(int i = 1;i <= N;i++) {
		for(int r = 0;r < v[i];r++) {
			queue.clear();
			for(int j = r;j <= M;j += v[i]) {
				while(!queue.isEmpty()&&j-queue.peekFirst()>s[i]*v[i]) queue.pollFirst();
				while(!queue.isEmpty()&&dp[i-1][queue.peekLast()]+(j-queue.peekLast())/v[i]*w[i]<=dp[i-1][j]) queue.pollLast();
				queue.add(j);
				dp[i][j] = dp[i-1][queue.peekFirst()]+(j-queue.peekFirst())/v[i]*w[i];
			}
		}
	}
	System.out.println(dp[N][M]);
}
}

多重背包就讲完了,主要讲了两种优化方法来应对数据规模较大的情况。

混合背包

在这里插入图片描述

混合背包就是01背包、完全背包、多重背包的结合体,其实完全可以变成多重背包,因为多重背包的s[i]=1时就是01背包,s[i]=∞(用一个比较大的数)时就是完全背包,话不多说,直接看代码吧,这里用的最普通的代码,

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int k = scanner.nextInt();
		int[] v = new int[n + 1];
		int[] w = new int[n + 1];
		int[] s = new int[n + 1];

		for (int i = 1; i <= n; i++) {
			v[i] = scanner.nextInt();
			w[i] = scanner.nextInt();
			s[i] = scanner.nextInt();
			if (s[i] == -1) {
				s[i] = 1;
			}
			if (s[i] == 0) {
				s[i] = 1000000000;
			}
		}

		int[][] dp = new int[n + 1][k + 1];
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j < k + 1; j++) {
				for (int j2 = 0; j2 * v[i] <= j && j2 <= s[i]; j2++) {
					dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - j2 * v[i]] + j2 * w[i]);
//					dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - j2 * v[i]] + j2 * w[i]);//原本写的j2 = 1开始,可是这样不对
				}

			}
		}
		System.out.println(dp[n][k]);
	}
}

二维费用的背包问题

在这里插入图片描述
类似01背包,只是多了一个维度,那么这里再用之前介绍的动态规划思路来思考一下。
第一步:缩小规模。考虑n个物品,那我就先考虑1个物品,再考虑2个物品…,需要一个维度表示当前考虑的物品个数。
第二步:限制。
(1)所选物品个数不能超过背包容量,那么需要一个维度记录当前背包的容量。
(2)所选物品个数不能超过背包重量,那么需要一个维度记录当前背包的重量。
第三步:写出dp数组。dp[i][j][k]表示当前考虑了前i个物品,背包容量为j,重量为k时的最大价值。
第四步:推状态转移方程。dp[i][j][k]应该从哪里转移过来呢,必然是从前i-1个物品转移,我要考虑两种情况,对于第i个物品,可以选择要它,也可以不要它,如果要第i个物品,我就需要背包里面给我预留出第i个物品的体积和重量,也就是从a[i-1][j-v[i]][k-m[i]]转移,同时也能获得该物品的价值。如果不要第i个物品,那么之前从前一个状态相同容量的背包转移过来就行,即a[i-1][j][k]。
综上状态转移方程如下
a [ i ] [ j ] = m a x ( a [ i − 1 ] [ j ] [ k ] , a [ i − 1 ] [ j − v [ i ] ] [ k − m [ i ] ] + w [ i ] ) a[i][j] = max(a[i-1][j][k],a[i-1][j-v[i]][k-m[i]]+w[i]) a[i][j]=max(a[i1][j][k],a[i1][jv[i]][km[i]]+w[i])
考虑写代码了
第一步:确定好遍历顺序,对于背包问题,一般第一个for遍历规模,第二个for遍历限制,但是我们有两个限制所以需要两个嵌套的for循环分别表示容量和重量。
代码如下,

import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int k = scanner.nextInt();//容积
		int mm = scanner.nextInt();//重量
		int[] v = new int[n+1];//容积
		int[] w = new int[n+1];
		int[] m = new int[n+1];//重量
		for (int i = 1; i < n+1; i++) {
			v[i] = scanner.nextInt();//容积
			m[i] = scanner.nextInt();//重量
			w[i] = scanner.nextInt();
		}
		int[][][] dp = new int[n+1][k + 1][mm + 1];
		for (int j = 1; j <= n; j++) {
			for (int i = 1; i <= k; i++) {
				for (int q = 1; q <= mm; q++)
					{
					dp[j][i][q] = dp[j-1][i][q];
					if(i>=v[j]&&q>=m[j])
					    dp[j][i][q] = Math.max(dp[j][i][q], dp[j-1][i - v[j]][q - m[j]] + w[j]);
					}
			}
		}
        //for (int i = 1; i < dp.length; i++) {
			
		//}
		System.out.println(dp[n][k][mm]);
	}
}

分组背包问题


分组背包的关键在于每一组的物品只能选一个,这个要求我们只需要在选物品的时候来进行约束即可,因此dp数组的含义还是和原来一样的。本来是每个物品遍历一次取一个最大值,现在是遍历某一组的物品,选取一个最大值。其实对于完全背包而言,遍历选择多个物品的情况,然后在其中选一个最大值也相当于一个分组,直接看代码吧,


import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
	static class good{
		int v,w;
		public good(int v, int w) {
			super();
			this.v = v;
			this.w = w;
		}	
	}
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	int n = scanner.nextInt();
	int V = scanner.nextInt();
	List<good> goods[] = new ArrayList[n+1];
	for (int i = 1; i < goods.length; i++) {
		goods[i] = new ArrayList<good>();
		int m = scanner.nextInt();
		for (int j = 0; j < m; j++) {
			goods[i].add(new good(scanner.nextInt(), scanner.nextInt()));
		}
	}
	int[][] dp = new int[n+1][V+1];
	for (int i = 1; i < dp.length; i++) {
		for (int k = 0; k < V+1; k++) {
			dp[i][k] = dp[i-1][k];
			for (int j = 0; j < goods[i].size(); j++) {
				if(goods[i].get(j).v<=k)
				    dp[i][k] = Math.max(dp[i][k], dp[i-1][k-goods[i].get(j).v]+goods[i].get(j).w);
			}
		}	
	}
	System.out.println(dp[n][V]);
}
}

简要分析一下代码,用good去存每一个分组,good[i]存的是第i个分组的物品。
遍历的时候先遍历分组,再遍历背包容量,然后遍历当前分组可以使用的物品,取一个最大值。

有依赖的背包问题

在这里插入图片描述
在这里插入图片描述
保证所有物品构成一颗树,如果选择了儿子节点,父节点必须选择。这里其实有一点像树形dp了,树形dp的dp数组定义套路和背包dp不太一样。
定义dp数组
第一步:缩小规模。这里考虑的是一棵树,考虑以root节点为根节点的树,然后root节点有son节点,为了缩小规模,接着考虑以son节点为根节点的树。
第二步:限制。所选物品个数不能超过物品容量,那么需要一个维度记录当前背包的容量。
第三步:写出dp数组。dp[i][j]表示当前考虑了以节点i为根节点的子树所具有的物品,背包容量为j时的最大价值。
第四步:推状态转移方程。dp[i][j]应该从哪里转移过来呢,必然是从i的儿子节点son转移过来。对于son来说他有多个不同的状态dp[son][k],在转移的时候j需要遍历,k也需要遍历,所以有两层嵌套的for循环。
为了满足这个条件,我们从根节点开始遍历,一般这种遍历就需要dfs了,从根节点进入就是dfs(root),dfs(root)返回的是以root为根的子树的最大值,也就是物品考虑了以root为父节点的所有物品可以选出来的最大值。
然后遍历root的所有儿子节点,这里用的链式前向星存储节点,其实对于java而言,用list或者hashmap存节点都可以。

for (int i = h[u]; i != -1 ; i = ne[i]) {
		 int son = e[i];
		}
	}

对于以儿子节点为根的所有物品,我有选择和不选两种操作,但是如果我要选择儿子节点,我一定要选择父节点,所以在选择儿子节点时我要预留出父节点的位置。所以下面代码是从m-v[u]开始的,减掉的是给父节点预留的,第一个for循环遍历的是以父节点为根的物品在不同的背包容量下的结果,第二个for循环遍历的是以儿子节点为根的物品在不同的背包容量下的结果。

 for (int j = m - v[u]; j >= 0; j--) {//u节点的所有可能的重量的组合
			 for (int k = 0; k <= j; k++) {//u节点的子节点可以选的重量的组合
				 dp[u][j] = Math.max(dp[u][j], dp[u][j-k]+dp[son][k]);
			}	
		}

在进行这个操作之前,以儿子节点为根的物品在不同的背包容量下的结果应该已经求出来了。所以在for循环之前有dfs(son)。
所有儿子节点都考虑完了之后,把当前父节点加进去。

	for (int i = m; i >= v[u]; i--) {
		dp[u][i] = dp[u][i-v[u]] + w[u];
	}
	for (int i = 0; i < v[u]; i++) {
		dp[u][i] = 0;
	}

全部代码如下,


import java.util.Arrays;
import java.util.Scanner;

public class Main{
	static int[] h;
	static int[] ne;
	static int[] e;
	static int idx;
	static int dp[][];
	static int[] w;
	static int[] v;
	static int n;
	static int m;
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	n = scanner.nextInt();
	m = scanner.nextInt();
	w = new int[n+1];//价值
	v = new int[n+1];//体积
	h = new int[n+1];
	ne = new int[n+1];
	Arrays.fill(h, -1);
	e = new int[n+1];
	dp = new int[n+1][m+1];
	int s;
	int root = -1;
	for (int i = 1; i < n+1; i++) {
		v[i] = scanner.nextInt();
		w[i] = scanner.nextInt();
		s = scanner.nextInt();
		if(s == -1) {
			root = i;
		}else {
			add(s,i);
		}
	}
    dfs(root);
    System.out.println(dp[root][m]);
}
private static void dfs(int u) {
	for (int i = h[u]; i != -1 ; i = ne[i]) {
		 int son = e[i];
		 dfs(son);
		 for (int j = m - v[u]; j >= 0; j--) {//u节点的所有可能的重量的组合
			 for (int k = 0; k <= j; k++) {//u节点的子节点可以选的重量的组合
				 dp[u][j] = Math.max(dp[u][j], dp[u][j-k]+dp[son][k]);
			}			
		}
	}
	for (int i = m; i >= v[u]; i--) {
		dp[u][i] = dp[u][i-v[u]] + w[u];
	}
	for (int i = 0; i < v[u]; i++) {
		dp[u][i] = 0;
	}
}
private static void add(int a, int b) {
	// TODO Auto-generated method stub
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}
}

这里我们要明确一点,就是如果当前背包容量可以让我们选儿子节点,那么就是不选白不选,区别在于不能选全部儿子节点时要选哪一个儿子节点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1445228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring 如何解决循环依赖?Spring三级缓存

什么是循环依赖 说白是一个或多个对象实例之间存在直接或间接的依赖关系&#xff0c;这种依赖关系构成了构成一个环形调用。 自己依赖自己 两个对象间的依赖关系 多个对象间的依赖关系 Spring出现循环依赖的场景 单例的setter注入 Service public class A {Resourceprivate…

【精选】java多态进阶——多态练习测试

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

InternLM大模型实战-4.XTuner大模型低成本微调实战

文章目录 前言笔记正文XTuner支持模型和数据集 微调原理跟随文档学习快速上手自定义微调准备数据准备配置文件 MS-Agent微调 前言 本文是对于InternLM全链路开源体系系列课程的学习笔记。【XTuner 大模型单卡低成本微调实战】 https://www.bilibili.com/video/BV1yK4y1B75J/?…

【MySQL进阶之路】生产案例:大量数据刷盘导致的数据库性能抖动问题优化

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

从汇编角度解释线程间互斥-mutex互斥锁与lock_guard的使用

多线程并发的竞态问题 我们创建三个线程同时进行购票&#xff0c;代码如下 #include<iostream> #include<thread> #include<list> using namespace std; //总票数 int ticketCount100; //售票线程 void sellTicket(int idx) {while(ticketCount>0){cou…

从零开始实现消息队列(二)

从零开始实现消息队列 .核心API交换机类型持久化网络通信Connection和Channel 消息应答模块划分 . 核心API 对于Broker来说,要实现以下核心API,通过这些API来实现消息队列的基本功能. 创建队列(queueDeclare)销毁队列(queueDelete)创建交换机(exchangeDeclare)销毁交换机(exc…

第4集《佛说四十二章经》

请大家打开讲议第四面&#xff0c;第一章&#xff0c;出家证果。 佛言&#xff1a;辞亲出家&#xff0c;识心达本&#xff0c;解无为法&#xff0c;名曰沙门。 在经文的刚开始啊&#xff0c;佛陀把修道的沙门提出了两个基本的条件&#xff1a; 第一个是辞亲出家&#xff0c;…

【Linux技术宝典】Linux入门:揭开Linux的神秘面纱

文章目录 官网Linux 环境的搭建方式一、什么是Linux&#xff1f;二、Linux的起源与发展三、Linux的核心组件四、Linux企业应用现状五、Linux的发行版本六、为什么选择Linux&#xff1f;七、总结 Linux&#xff0c;一个在全球范围内广泛应用的开源操作系统&#xff0c;近年来越来…

MySQL数据库-索引概念及其数据结构、覆盖索引与回表查询关联、超大分页解决思路

索引是帮助mysql高效获取数据的数据结构,主要用来提高检索的效率,降低数据库的IO成本(输入输出成本&#xff08;Input-Output Cost&#xff09;),同时通过索引对数据进行排序也能降低数据排序的成本,降低了CPU的消耗。 Mysql的默认存储引擎InnoDB&#xff0c;InnoDB采用的B树的…

代码随想录算法训练营第四十九天(动态规划篇)| 474. 一和零, 完全背包理论基础

474. 一和零 题目链接&#xff1a;https://leetcode.cn/problems/ones-and-zeroes/submissions/501607337/ 思路 之前的背包问题中&#xff0c;我们对背包的限制是容量&#xff0c;即每个背包装的物品的重量和不超过给定容量&#xff0c;这道题的限制是0和1的个数&#xff0…

Acwing 5469. 有效点对【正难则反+巧妙选择根节点】

原题链接&#xff1a;https://www.acwing.com/problem/content/5472/ 题目描述&#xff1a; 给定一个 n 个节点的无向树&#xff0c;节点编号 1∼n。 树上有两个不同的特殊点 x,y&#xff0c;对于树中的每一个点对 (u,v)(u≠v)&#xff0c;如果从 u 到 v 的最短路径需要经过…

【MySQL基础】:深入探索DQL数据库查询语言的精髓(上)

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. DQL1.1 基本语法1.2 基础查询1.3 条件查询1.3 聚合函数 &#x1f324;️ 全篇…

备战蓝桥杯---动态规划(理论基础)

目录 动态规划的概念&#xff1a; 解决多阶段决策过程最优化的一种方法 阶段&#xff1a; 状态&#xff1a; 决策&#xff1a; 策略&#xff1a; 状态转移方程&#xff1a; 适用的基本条件 1.具有相同的子问题 2.满足最优子结构 3.满足无后效性 动态规划的实现方式…

寻找最优的路测线 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 评估一个网络的信号质量&#xff0c;其中一个做法是将网络划分为栅格&#xff0c;然后对每个栅格的信号质量计算。 路测的时候&#xff0c;希望选择一条信号最好…

[leetcode] 33. 搜索旋转排序数组

文章目录 题目描述解题方法二分查找java代码复杂度分析 题目描述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组…

防火墙安全策略及nat实验

要求一&#xff1a;生产区的设备在工作时间访问dmz区,仅可访问http服务器 要求二&#xff1a;办公区可以全天访问dmz区&#xff0c;其中10.0.2.20可以访问FTP服务器和HTTP服务器&#xff0c;10.0.2.10仅可以ping通10.0.3.10 要求三&#xff1a;办公区在访问服务器区时采用匿名认…

基于数据挖掘的微博事件分析与可视化大屏分析系统

设计原理&#xff0c;是指一个系统的设计由来&#xff0c;其将需求合理拆解成功能&#xff0c;抽象的描述系统的模块&#xff0c;以模块下的功能。功能模块化后&#xff0c;变成可组合、可拆解的单元&#xff0c;在设计时&#xff0c;会将所有信息分解存储在各个表中&#xff0…

使用C#快速创建一个非常实用的桌面应用程序

过节时和我年纪轻轻就一把年纪的弟弟张老二闲聊了许久&#xff0c;发现他对编程产生了一泡浓厚的兴趣&#xff0c;于是我就给他漏了一手C#&#xff0c;做了一个简单的适用于win-x64配置cpu的桌面应用程序。 步骤如下&#xff1a; 1.打开Visual Studio,点击新建项目&#xff0…

iOS AlDente 1.0自动防过充, 拯救电池健康度

经常玩iOS的朋友可能遇到过长时间过充导致的电池鼓包及健康度下降问题。MacOS上同样会出现该问题&#xff0c;笔者用了4年的MBP上周刚拿去修了&#xff0c;就是因为长期不拔电源的充电&#xff0c;开始还是电量一半的时候不接电源会黑屏无法开机&#xff0c;最后连着电源都无法…

【华为 ICT HCIA eNSP 习题汇总】——题目集12

1、企业网络内部常常采用私有 IP 地址进行通信&#xff0c;以下哪个地址属于私有 IP 地址&#xff1f; A、0.1.1.1 B、127.5.4.3 C、128.0.0.5 D、172.24.35.36 考点&#xff1a;网络层 解析&#xff1a;&#xff08;D&#xff09; A类 IP 地址中&#xff0c;10.0.0.0 ~ 10.255…