第五章 动态规划(一)——背包问题

news2024/11/25 13:10:17

文章目录

    • 01背包问题
    • 完全背包
    • 多重背包
    • 分组背包
    • 背包练习题
      • 2. 01背包问题
      • 3. 完全背包问题
      • 4. 多重背包问题 I
      • 5. 多重背包问题 II
      • 9. 分组背包问题

01背包问题

n个物品,每个物品的体积为 v i v_i vi,价值为 w i w_i wi,每次只能选择一个物品放入背包
在背包能装下的情况下,价值最大的装法?

每个物品最多只能使用1次
image.png

dp问题从两方面考虑,状态表示状态计算
状态表示:每个状态都可以用不同的维度描述,01背包问题中,每种选法对应不同的状态,设 f ( i , j ) f(i, j) f(i,j)为状态

  • 集合:所有的状态构成了一个集合,也就是所有的选法构成了一个集合
    • 考虑集合中的状态可以由哪些条件限制,背包问题中有两个条件
      • 从前i个物品中选择
      • 选择的物品总体积 < = j <= j <=j
  • 属性:最大值,最小值,数量,背包问题的属性为最大值

状态表示从两个方面考虑,集合以及集合的属性。根据题意,将所有状态(选择)看成集合,同时用条件限制集合中的状态。也就是将状态用条件表示出来,同时根据题意思考集合(状态)表示的含义,也就是属性。一般的属性有最大值,最小值以及数量

状态计算:常说的状态转移方程,本质就是集合的划分,如何将集合划分成更小的子集,同时保证子集中的元素不重不漏?
最常用的思考角度是:选择某一条件,不具有该条件为一个集合,具有该条件为一个集合
在01背包问题中,可以考虑不选择第i个物品以及选择第i个物品为两个不同集合,这两个不同集合构成了状态 f ( i , j ) f(i, j) f(i,j)

  • 不选择第i个物品时,集合的含义是:从前i个物品中选择,但不选择第i个物品,此时能获得的最大价值。可以表述成从前i-1个物品中选择,此时能获得的最大价值。即 f ( i − 1 , j ) f(i-1, j) f(i1,j)
  • 选择第i个物品时,集合的含义是:从前i个物品中选择,且必须选择第i个物品,此时能获得的最大价值。举个例子:若一场考试中,小明考了最高分,将所有人的分数减去10分,小明还是最高分。选择的物品中,注定要选择第i个物品的话,在还未选择第i个物品之前, f ( i , j ) f(i, j) f(i,j)就已经确定。只不过我们需要加上限制,从 f ( i − 1 , j − v i ) f(i-1, j - v_i) f(i1,jvi)中推导。为什么需要这个限制?很显然,最高分加上成绩后不能超过上限,最低分减去成绩后不能低过下限

先把所有物品中的第i个物品去掉,求出此时的最大值,然后加上第i个物品的价值

由于所有的选法都包含第i个物品,要求从1i个物品中选择物品的最大价值时,可以将每种选法的第i个物品都去掉,此时的最大价值加上第i个物品的价值也是从1n个物品中选择物品的最大价值

那么状态就变成了从1~i - 1个物品中选,并且体积不超过v- v i v_i vi的最大价值
f ( i − 1. j − v i ) + w i f(i - 1. j - v_i) + w_i f(i1.jvi)+wi

最后两个集合不重不漏构成了 f ( i , j ) f(i, j) f(i,j)这个集合,由于集合的属性是选择的最大价值,所以 f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ) f(i, j) = max(f(i-1, j), f(i-1, j-v_i)+w_i) f(i,j)=max(f(i1,j),f(i1,jvi)+wi)
同时考虑初始状态, f ( i , 0 ) = 0 f(i, 0) = 0 f(i,0)=0:背包容量为0时,无法装入物品,所以总价值为0
f ( 0 , i ) = 0 f(0, i) = 0 f(0,i)=0,从前0个物品中选择物品装入背包,此时的总价值也为0


完全背包

与01背包不同,每个物品能使用无限次

状态表示与01背包相同,不同的是状态计算
如何划分 f ( i , j ) f(i, j) f(i,j)这个集合?依然是从两个方面,选择第i个物品以及不选择第i个物品
不选择第i个物品时的状态为 f ( i − 1 , j ) f(i-1, j) f(i1,j)
选择第i个物品时,由于物品能选择无限次,直到背包装不下为止,假设背包能装下n个第i个物品,那么此时的状态为 f ( i − 1 , j − k ∗ v i ) f(i-1, j-k*v_i) f(i1,jkvi),其中k从1到n
这些集合不重不漏,组成了 f ( i , j ) f(i, j) f(i,j),考虑集合的属性为选择物品的最大价值,所以
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, 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,j)=max(f(i1,j),f(i1,jvi)+wi,f(i1,j2vi)+2wi,...)
根据这个表达式,就有了完全背包的朴素解法

for (int i = 1; i <= n; ++ i )
{
	for (int j = 1; j <= m; ++ j )
	{
		for (int k = 0; k * v[i] <= j; ++ k )
		{
			f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
		}
	}
}
    
printf("%d\n", f[n][m]);

正常情况下,这个做法将超时

f ( i , j − v i ) = m a x ( f ( i − 1 , j − v i ) , f ( i − 1 , j − 2 ∗ v i ) + w i , f ( i − 1 , j − 3 ∗ v i ) + 2 ∗ w i , . . . ) f(i, j-v_i) = max(f(i-1, j-v_i), f(i-1, j-2*v_i) + w_i, f(i-1, j-3*v_i) + 2*w_i, ...) f(i,jvi)=max(f(i1,jvi),f(i1,j2vi)+wi,f(i1,j3vi)+2wi,...)
将这个状态与 f ( i , j ) f(i, j) f(i,j)进行对比:
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, 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,j)=max(f(i1,j),f(i1,jvi)+wi,f(i1,j2vi)+2wi,...)
image.png
可以得到等式: f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i , j − v i ) + w i ) f(i, j) = max(f(i-1, j), f(i, j-v_i) + w_i) f(i,j)=max(f(i1,j),f(i,jvi)+wi)
此时线性的枚举就被优化掉,得到一个二维dp

for (int i = 1; i <= n; ++ i )
{    
	for (int j = 1; j <= m; ++ j )
	{
		f[i][j] = f[i - 1][j];
		if (v[i] <= j) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
	}   
}
printf("%d\n", f[n][m]);

压缩后

for (int i = 1; i <= n; ++ i )
	for (int j = v[i]; j <= m; ++ j )
		f[j] = max(f[j], f[j - v[i]] + w[i]);
		
printf("%d\n", f[m]);

注意:j不是从后往前更新的,因为在二维dp中 f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]) f[i][j]=max(f[i][j],f[i][jv[i]]+w[i])
其中 f [ i ] [ j − v [ i ] ] + w [ i ] f[i][j - v[i]] + w[i] f[i][jv[i]]+w[i]是当前行i的状态,需要先更新
而01背包问题的压缩中, f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]) f[i][j]=max(f[i][j],f[i1][jv[i]]+w[i])
f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i - 1][j - v[i]] + w[i] f[i1][jv[i]]+w[i]是上一行的状态,若从前往后更新,上一行的状态将被覆盖,所以从后往前更新


多重背包

使用次数有限,每个物品有 s i s_i si个,也就是最多使用 s i s_i si
朴素解法与完全背包一样,只是需要添加限制:每个物品的选择次数不能超过 s i s_i si

for (int i = 1; i <= n; ++ i )
	for (int j = 1; j <= m; ++ j )
		for (int k = 0; k <= s[i] && k * v[i] <= j; ++ k )
			f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
			
printf("%d\n", f[n][m]);

正常情况下,该解法将超时,那么如何优化朴素解法?

多重背包的状态表示和之前的背包问题一样,只有状态计算不一样
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, 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,j)=max(f(i1,j),f(i1,jvi)+wi,f(i1,j2vi)+2wi,...)
可以表示成 f ( i , j ) = m a x ( f ( i − 1 , j − k ∗ v [ i ] ) + k ∗ w [ i ] ) f(i, j) = max(f(i-1, j-k*v[i]) + k*w[i]) f(i,j)=max(f(i1,jkv[i])+kw[i]),其中k从0到 s i s_i si
式子的形式和完全背包很像,可以用完全背包的优化方法优化多重背包吗?
考虑状态两个状态 f ( i , j ) f(i, j) f(i,j) f ( i , j − v i ) f(i, j-v_i) f(i,jvi)
image.png
与完全背包不同,多重背包的 f ( i , j − v i ) f(i, j-v_i) f(i,jvi) f ( i , j ) f(i, j) f(i,j)的最后多出了一项,由于这一项的存在, f ( i , j − v i ) f(i, j-v_i) f(i,jvi)就推导不出 f ( i , j ) f(i, j) f(i,j)
为什么完全背包的 f ( i , j − v i ) f(i, j-v_i) f(i,jvi)的最后不会多出一项?
区别就在于完全背包可以选择无限次,直到背包能装下k个物品,却装不下(k+1)个物品,此时k最大,背包能装最多的第i个物品
完全背包的 f ( i , j ) f(i, j) f(i,j) f ( i , j − v i ) f(i, j-v_i) f(i,jvi)中,两者最后一项的k都是最大的,即 f ( i − 1 , j − k ∗ v i ) f(i-1, j-k*v_i) f(i1,jkvi)中, j − k ∗ v i > 0 j-k*v_i>0 jkvi>0 j − ( k + 1 ) ∗ v i < 0 j-(k+1)*v_i<0 j(k+1)vi<0
多重背包的 f ( i , j ) f(i, j) f(i,j)的最后一项为 f ( i − 1 , j − k ∗ v i ) + k ∗ w i f(i-1, j-k*v_i) + k*w_i f(i1,jkvi)+kwi,其中 j − k ∗ v i > 0 j-k*v_i>0 jkvi>0 k = s i k=s_i k=si,等于题目限制的最多选择第i个物品的数量

  • j − ( k + 1 ) ∗ v i > 0 j-(k+1)*v_i>0 j(k+1)vi>0,那么 f ( i , j − v i ) f(i, j-v_i) f(i,jvi)的最后一项为 f ( i − 1 , j − ( k + 1 ) ∗ v i ) + k ∗ w i f(i-1, j-(k+1)*v_i) + k*w_i f(i1,j(k+1)vi)+kwi,此时无法推导 f ( i , j ) f(i, j) f(i,j)
  • j − ( k + 1 ) ∗ v i < 0 j-(k+1)*v_i<0 j(k+1)vi<0,那么 f ( i , j − v i ) f(i, j-v_i) f(i,jvi)的最后一项为 f ( i − 1 , j − k ∗ v i ) + ( k − 1 ) ∗ w i f(i-1, j-k*v_i) + (k-1)*w_i f(i1,jkvi)+(k1)wi,此时可以推导 f ( i , j ) f(i, j) f(i,j)

所以多重背包与完全背包的优化不同, f ( i , j − v i ) f(i, j-v_i) f(i,jvi)无法推导 f ( i , j − v ) f(i, j-v) f(i,jv)的原因是:k最大时,多重背包的 k ∗ v i k*v_i kvi可能临界背包的容量,而完全背包的 k ∗ v i k*v_i kvi一定临界背包的容量,此时 f ( i , j − v i ) f(i, j-v_i) f(i,jvi)的最后一项不存在,因为背包无法装下

多重背包采用二进制优化:
优化之前,对于每个物品,我们需要试着将 s i s_i si个物品装入背包,以更新能选择的最大价值
这是一个线性的计算,完全背包能将这个线性的计算优化成常数级别的计算,多重背包能将其优化成O(logn) 级别的计算

将物品分组,每组的物品数量为 2 i 2^i 2i,同时每组的物品数量各不相同,并且只能被选择一次
假设能选择 s i s_i si个第i个物品,将 s i s_i si进行分组: 2 0 , 2 1 , 2 2 , . . . , 2 l o g s i 2^0, 2^1, 2^2, ... , 2^{logs_i} 20,21,22,...,2logsi,选择其中的任意组进行相加,就能得到(0~ s i s_i si)中的任何数

假设每组背包的数量为 2 0 , 2 1 , 2 2 2^0, 2^1, 2^2 20,21,22,在每组背包只选择一次的情况下,选择其中的任意组进行相加,得到的物品数量为(1~7)。即 2 0 , 2 1 , 2 2 2^0, 2^1, 2^2 20,21,22能构成1~7中任意一个数,比如 5 = 2 0 + 2 2 , 7 = 2 0 + 2 1 + 2 2 5=2^0+2^2, 7=2^0+2^1+2^2 5=20+22,7=20+21+22
此时多重背包问题就被转化成了01背包问题
一个线性的复杂度O(n) 就被优化成了O(logn)

假设 s i = 200 s_i=200 si=200,此时如何分组?先从最小的开始: 2 0 , 2 1 , 2 2 , 2 3 , 2 4 , 2 5 , 2 6 2^0, 2^1, 2^2, 2^3, 2^4, 2^5, 2^6 20,21,22,23,24,25,26,此时选择任意组进行相加,得到的物品数量为(0127),$127=2^7-1$。不能再分一组数量为$2^7$的背包,因为数量不够,只剩下73个物品。此时将剩下的物品为一组,已知之前的分组能组成的背包数量为(0127),(0127)中的任意数量与73相加能得到(73200),合并一下
那么 2 0 , 2 1 , 2 2 , 2 3 , 2 4 , 2 5 , 2 6 , 73 2^0, 2^1, 2^2, 2^3, 2^4, 2^5, 2^6, 73 20,21,22,23,24,25,26,73,选择任意组进行相加,得到的物品数量为(0~200)

推广一下,先将 s i s_i si分成 2 0 , 2 1 , 2 2 , . . . , 2 k 2^0, 2^1, 2^2, ... , 2^k 20,21,22,...,2k,此时能组成(0~ 2 k + 1 − 1 2^{k+1}-1 2k+11)之间的任何数
2 k + 1 − 1 < s i < 2 k + 2 − 1 2^{k+1}-1 < s_i < 2^{k+2}-1 2k+11<si<2k+21,说明 s i − ( 2 k + 1 − 1 ) < 2 k + 1 s_i-(2^{k+1}-1) < 2^{k+1} si(2k+11)<2k+1,用c表示: c < 2 k + 1 c < 2^{k+1} c<2k+1
此时我们要将c分为一组,使得 c + ( 2 k + 1 − 1 ) = s i c+(2^{k+1}-1) = s_i c+(2k+11)=si
由于之前的分组能组成(0$2^{k+1}-1$)之间的所有数,这些数中的任意数与数量为c的分组相加后,能组成(c 2 k + 1 − 1 + c 2^{k+1}-1+c 2k+11+c)中的所有数,也就是(c~ s i s_i si)之间的所有数
由于 c < 2 k + 1 c<2^{k+1} c<2k+1,所以可以将(0$2^{k+1}-1$)与(c s i s_i si)合并,最终 2 0 , 2 1 , 2 2 , . . . , 2 k , c 2^0, 2^1, 2^2, ... , 2^k, c 20,21,22,...,2k,c能组成(0~ s i s_i si)之间的所有数

s i s_i si进行二进制优化后,将优化后的问题看成01背包问题即可
模板就是01背包的模板,不同的是:用来动规的体积数组以及价值数组是预处理后的
如何预处理?根据 s i s_i si不断地划分出 2 i 2^i 2i,将每个物品的体积与容量乘以 2 i 2^i 2i

for (int i = 1; i <= n; ++ i ) // n为物品的数量
{
	scanf("%d%d%d", &pv, &pw, &s); // 读取该物品的体积,容积以及能选择的次数
	int k = 1;
	while (k <= s)
	{
		v[cnt] = k * pv;
		w[cnt] = k * pw;
		s -= k;
		k *= 2; // 注意这两个式子的先后顺序
		cnt ++ ;
	}
	if (s > 0)
	{
		v[cnt] = s * pv;
		w[cnt] = s * pw;
		cnt ++ ;
	}
}

分组背包

给定v组物品以及其中每个物品的价值与体积,每组物品只能选择一个物品,问如何选择装入最大价值?

状态表示:从前i组中选,体积不超过j的所有选法,属性为价值的最大值
状态计算: f ( i , j ) f(i, j) f(i,j)可以划分成两个集合,一个集合选择第i组物品中的某一个,一个集合不选择第i组物品,两个集合不重不漏组成 f ( i , j ) f(i, j) f(i,j)
不选择第i组物品的状态为 f ( i − 1 , j ) f(i-1, j) f(i1,j)
选择第i组物品的状态为 f ( i − 1 , j − v [ i , k ] ) f(i-1, j-v[i,k]) f(i1,jv[i,k]),其中k表示第i组物品中第k个物品,假设第i组有n个物品,那么k从1~n

合并两个状态,k就是从0~n

for (int i = 1; i <= n; ++ i )
{
	for (int j = 1; j <= m; ++ j )
	{
		f[i][j] = f[i - 1][j];
		for (int k = 1; k <= s[i]; ++ k )
		{
			if (v[i][k] <= j) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
		}
	}
}

printf("%d\n", f[n][m]);

若k从1~n,那么f[i][j] = f[i - 1][j];不能写到k循环中,若要写到k循环中,要取max:f[i][j] = max(f[i][j], f[i - 1][j]);,因为选择第i组的物品后,f[i][j]可能大于f[i-1][j]。之前的背包问题省略了max是因为是否取max不影响最后的结果
可以写成这样:

for (int i = 1; i <= n; ++ i )
{
	for (int j = 1; j <= m; ++ j )
	{		
		for (int k = 1; k <= s[i]; ++ k )
		{
			f[i][j] = max(f[i][j], f[i - 1][j]);
			if (v[i][k] <= j) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
		}
	}
}

printf("%d\n", f[n][m]);

也可以将k从0~n

for (int i = 1; i <= n; ++ i )
{
	for (int j = 1; j <= m; ++ j )
	{		
		for (int k = 0; k <= s[i]; ++ k )
		{			
			if (v[i][k] <= j) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
		}
	}
}

printf("%d\n", f[n][m]);

然后直接压缩状态

    for (int i = 1; i <= n; ++ i )
        for (int j = m; j >= 1; -- j )
            for (int k = 0; k <= s[i]; ++ k )
                if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

    
    printf("%d\n", f[m]);

注意j从后往前的更新


背包练习题

2. 01背包问题

2. 01背包问题 - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 1010;
int v[N], w[N]; // 体积与价值
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; ++ i )
    {
        for (int j = 1; j <= m; ++ j )
        {
            f[i][j] = f[i - 1][j];
            if (v[i] <= j) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }   
    
    printf("%d\n", f[n][m]);
    return 0;
}

滚动数组压缩

#include <iostream>
using namespace std;

const int N = 1010;
int v[N], w[N]; // 体积与价值
int f[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; ++ i )
        for (int j = m; j >= v[i]; -- j )
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    
    printf("%d\n", f[m]);
    return 0;
}

3. 完全背包问题

3. 完全背包问题 - AcWing题库
image.png

朴素解法,超时

#include <iostream>
using namespace std;

const int N = 1010;
int v[N], w[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; ++ i )
    {
        for (int j = 1; j <= m; ++ j )
        {
            for (int k = 0; k * v[i] <= j; ++ k )
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    }
    
    printf("%d\n", f[n][m]);
    
    return 0;
}

需要压缩的二维dp

#include <iostream>
using namespace std;

const int N = 1010;
int v[N], w[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; ++ i )
    {    
        for (int j = 1; j <= m; ++ j )
        {
            f[i][j] = f[i - 1][j];
            if (v[i] <= j) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
        }   
    }
    printf("%d\n", f[n][m]);
    
    return 0;
}

压缩后的一维dp

#include <iostream>
using namespace std;

const int N = 1010;
int v[N], w[N];
int f[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; ++ i )
        for (int j = v[i]; j <= m; ++ j )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
            
    printf("%d\n", f[m]);
    
    return 0;
}

4. 多重背包问题 I

4. 多重背包问题 I - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 110;
int v[N], w[N], s[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i) scanf("%d%d%d", &v[i], &w[i], &s[i]);
    
    for (int i = 1; i <= n; ++ i )
        for (int j = 1; j <= m; ++ j )
            for (int k = 0; k <= s[i] && k * v[i] <= j; ++ k )
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
                
    printf("%d\n", f[n][m]);
    
    return 0;
}

5. 多重背包问题 II

5. 多重背包问题 II - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 25000, M = 2010;
int v[N], w[N];
int f[M];


int main()
{
    int n, m, cnt = 1;
    scanf("%d%d", &n, &m);
    int pv, pw, s;
    for (int i = 1; i <= n; ++ i )
    {
        scanf("%d%d%d", &pv, &pw, &s);
        int k = 1;
        while (k <= s)
        {
            v[cnt] = k * pv;
            w[cnt] = k * pw;
            s -= k;
            k *= 2; // 注意这两个式子的先后顺序
            cnt ++ ;
        }
        if (s > 0)
        {
            v[cnt] = s * pv;
            w[cnt] = s * pw;
            cnt ++ ;
        }
    }
    
    
    
    n = cnt - 1;
    
    for (int i = 1; i <= n; ++ i )
        for (int j = m; j >= v[i]; -- j )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
            
    printf("%d\n", f[m]);
    return 0;
}

9. 分组背包问题

9. 分组背包问题 - AcWing题库

#include <iostream>
using namespace std;

const int N = 110;
int v[N][N], w[N][N], s[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i )
    {
        scanf("%d", &s[i]);
        for (int j = 1; j <= s[i]; ++ j )
            scanf("%d%d", &v[i][j], &w[i][j]);
    }
    
    for (int i = 1; i <= n; ++ i )
    {
        for (int j = 1; j <= m; ++ j )
        {
            f[i][j] = f[i - 1][j];
            for (int k = 1; k <= s[i]; ++ k )
            {
                if (v[i][k] <= j) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
    }
    
    printf("%d\n", f[n][m]);
    
    return 0;
}

压缩状态后:

#include <iostream>
using namespace std;

const int N = 110;
int v[N][N], w[N][N], s[N];
int f[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i )
    {
        scanf("%d", &s[i]);
        for (int j = 1; j <= s[i]; ++ j )
            scanf("%d%d", &v[i][j], &w[i][j]);
    }
    
    for (int i = 1; i <= n; ++ i )
    {
        for (int j = m; j >= 1; -- j )
        {
            for (int k = 0; k <= s[i]; ++ k )
            {
                if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
            }
        }
    }
    
    printf("%d\n", f[m]);
    
    return 0;
}

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

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

相关文章

JavaScript的库——jquery

JavaScript的库——jquery 一、jQuery的hello 将代码写在一个闭包中 二、面试题&#xff1a; jQuery的ready函数和JavaScript的onload事件有什么区别&#xff1f; 是null&#xff0c;因为拿不到box&#xff0c;代码在后面&#xff0c;还没有执行。 所以需要将代码放在后面&am…

ControlNet新玩法!一键生成AI艺术二维码QR

AI绘画又又又又火出圈了&#xff01;这波来源于AI人所发掘的ControlNet for QR Code 话不多说&#xff0c;看看效果图 这张看着极具个性的图像正是一个可以扫描的二维码&#xff0c;通过使用Stable Diffusion和Controlnet生成。由此定制生成个人艺术二维码&#xff0c;选择不同…

前端学习——JS进阶 (Day4)

深浅拷贝 练习 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice…

Antd的Select组件二次封装

提示&#xff1a;Select组件二次封装的目的,是为了在系统里面更方便、简洁地使用Select 这是官方写的使用方法是: import React from react; import { Select } from antd;const handleChange (value: string) > {console.log(selected ${value}); };const App: React.FC …

信息泄露与大数据:隐私安全的挑战与对策

随着大数据时代的到来&#xff0c;我们生活的方方面面都与数据息息相关。然而&#xff0c;随之而来的信息泄露问题也日益严重&#xff0c;给个人隐私和数据安全带来了巨大挑战。本文将围绕信息泄露与大数据展开讨论&#xff0c;探讨其中的问题、原因以及如何应对。 山海鲸大屏 …

langchain +SQLDatabase+Mysql+OpenAI

通过langchain SQLDatabaseMysql来获取数据 参照:https://github.com/hwchase17/langchain/issues/2333 from langchain import OpenAI, SQLDatabase, SQLDatabaseChain from sqlalchemy import create_enginefrom common.keys import OPENAI_API_KEYengine create_engine(m…

Network Dissection 论文阅读笔记

Network Dissection 论文阅读笔记 1. 简介2. 网络刨析2.1 深度视觉表征的可解释性的测量步骤2.2 数据集2.3 可解释神经元评分 3. 实验3.1 对解释的人类评价3.2 Measurement of Axis-Aligned Interpretability3.3 理解层概念3.4 网络架构和监督3.5 训练条件 vs 可解释性3.6 网络…

【二等奖方案】基于人工智能的漏洞数据分类赛题「道可道,非常道」团队解题思路

2022 CCF BDCI 大赛 数字安全公开赛「基于人工智能的漏洞数据分类」赛题二等奖团队「道可道&#xff0c;非常道」战队获奖方案&#xff0c;赛题地址&#xff1a; http://go.datafountain.cn/s57 团队简介 本团队具有丰富的比赛和项目经验。在AI大赛上多次拿到Top成绩&#xf…

2.Docker镜像和容器操作

文章目录 Docker操作Docker镜像操作搜索镜像获取镜像镜像加速下载查看镜像详细信息为镜像添加标签删除镜像导出导入镜像上传镜像 Docker容器操作创建容器查看容器状态启动容器创建并启动容器进入容器停止容器删除容器复制容器文件到宿主机容器的导出导入 Docker操作 ###查看do…

uniapp中超好用(且免费)的安全类插件推荐!(持续更新中)

前几天写了一篇【干货分享】uniapp做的安卓App如何加固&#xff0c;发现收藏的人蛮多的。所以说&#xff0c;更加证明了我说的第一个问题&#xff1a;现在用uniapp的人是越来越多了。 而通过使用uniapp上自带的插件&#xff0c;也是能够实现事半功倍的效果&#xff0c;让不懂前…

简单记录一下剑指offer的编程题(C语言实现)——替换空格反转链表

1. 替换空格 (剑指 Offer 05) 请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20"。 示例&#xff1a; 输入&#xff1a;s "We are happy." 输出&#xff1a;"We%20are%20happy."简单分析&#xff1a; 新建一个数组&#xff0c;…

《SpringBoot篇》16.SpringBoot整合Elasticsearch

陈老老老板 说明&#xff1a;工作了&#xff0c;学习一些新的技术栈和工作中遇到的问题&#xff0c;边学习边总结&#xff0c;各位一起加油。需要注意的地方都标红了&#xff0c;还有资源的分享. 一起加油。 本文是介绍Elasticsearch用法与SpringBoot整合 1.ES简介 注&#xf…

Solus Linux 4.4版本发布

导读Solus Linux近日更新到4.4版本&#xff0c;作为这个受爱好者欢迎的Linux发行版的最新增量更新&#xff0c;团队仍在努力实现其基于Serpent OS和对Linux发行版的其他重大基本变化的纳入。 Solus 4.4带有各种各样的更新包&#xff0c;还有一些其他的改变&#xff0c;比如现在…

【C++学习记录】(二)--一个C++工程文件里有哪些东西?

写在前面 首先&#xff0c;我有一个完整的C工程文件&#xff0c;文件分别是包含Debug、include、Service和src。 1.了解文件结构: 首先&#xff0c;查看每个文件夹中的内容以了解文件的组织结构。Debug文件夹通常包含与调试相关的文件&#xff0c;include文件夹可能包含头文件…

Innovus:highlight clock tree(高亮时钟树方法)

过往文章链接&#xff1a; ICC2: highlight clock tree&#xff08;高亮时钟树方法&#xff09; innovus也有高亮clock tree的方法 在ccopt之后&#xff0c;使用ctd_win命令打开Clock Tree Debugger (CTD) window。 ctd_trace -from $root_pin -to $sink_pin -color $color ;#…

产品介绍|持续机器视觉产品的研发与迭代,赋能制造行业智慧生产

当前&#xff0c;以人工智能为代表的新一代信息技术正在加速推动社会各领域优化升级&#xff0c;对于行业自动化、智能化的要求越来越高&#xff0c;作为人工智能重要领域之一的机器视觉技术&#xff0c;迎来了快速发展。 机器视觉用机器代替人眼来做测量和判断&#xff0c;通…

STM32MP157驱动开发——设备树知识

文章目录 概述开发板中设备树文件目录结构设备树文件语法设备树文件参考实例&#xff1a;Devicetree 格式DTS 文件的格式node 的格式properties 的格式 dts 文件包含 dtsi 文件常用的属性#address-cells 、#size-cellscompatiblemodelstatusreginterruptgpio属性name( 过时了&a…

SSM框架整合

1.创建数据库表 数据库环境搭建 CREATE DATABASE ssmbuild;USE ssmbuildDROP TABLE IF EXISTS books;CREATE TABLE books( bookID INT(10) NOT NULL AUTO_INCREMENT COMMENT 书id, bookName VARCHAR(100) NOT NULL , bookCounts INT(11) NOT NULL , detail VARCHAR(200) NOT …

【Netty】NIO基础(三大组件、文件编程)

文章目录 三大组件Channel & BufferSelector ByteBufferByteBuffer 正确使用姿势ByteBuffer 内部结构ByteBuffer 常见方法分配空间向 buffer 写入数据从 buffer 读取数据mark 和 reset 字符串与 ByteBuffer 互转Scattering ReadsGathering Writes粘包、半包分析 文件编程Fi…

美团青龙教程(2023.3.2最新版)附脚本

这次直接放上leaffish大佬的库地址吧&#xff0c;美团脚本也在里面 &#xff08;此为最新脚本&#xff09; ql raw https://raw.githubusercontent.com/leafTheFish/DeathNote/main/meituanV3.js /* 美团 v3.02 新版美团仅支持青龙等nodejs环境, 不支持圈X 自动领券和完成一…