Dp 背包问题

01背包问题

问题描述

有 n 个物品和一个容量为 v 的背包,每个物品有两个属性,一个是其体积 vi ,一个是其价值(权重) wi ,每件物品最多只能用一次(每件物品只有一个,可用可不用),将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大

Dp 思想

左边的情况一定存在,但右边的情况不一定会存在,当 j<vi 的时候,右边的情况就不存在,故需要进行判断

实现

//f[0][0~m]=0,一件物品都没有选,所以其最大价值为0
	for(int i=1;i<=n;i++)
		for (int j = 0;j <= m;j++) {
			f[i][j] = f[i - 1][j];
			if (j >= v[i])
				f[i][j] = max(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[j] = max(f[j], f[j - v[i]] + w[i]);

= f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]); j-v[i]<j,j 从小到大枚举,计算 f[j] 的时候,f[j-v[i]] 在第 i 层已经计算过了 f[j - v[i]]=f[i][j-v[i]]

//f(i) 计算时只用到了 f(i-1),f(0)~f(i-2) 是没有用的,可以用滚动数组完成
//j 和 j-wi  都是 ≤ j
for(int i=1;i<=n;i++)
	for (int j = 0;j <= m;j++) {//--> j<v[i]是没有意义的——>j=v[i],判断也可以删除
                                //--> int j=m;j>=v[i];j--
		f[i][j] = f[i - 1][j];//--> f[j]=f[j] 恒等式,可以删去
		if (j >= v[i])//--> 删去
			f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
              //-->f[j] = max(f[j], f[j - v[i]] + w[i]);
              //== f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]); 
              //j-v[i]<j,j 从小到大枚举,计算 f[j] 的时候,f[j-v[i]] 在第 i 层已经更新过
              //存的是f[i][j-v[i]],而不是f[i-1][j-v[i]]
              //故f[j - v[i]]=f[i][j-v[i]]
              //若想要使得f[j-v[i]]=f[i-1][j-v[i]],需要 j 从大到小进行遍历
              //此时,f[j-v[i]] 在第 i 层没有更新,仍然存的是 f[i-1][j-v[i]]
              //f[j-v[i]]=f[i-1][j-v[i]]
    }


//最终代码
for(int i=1;i<=m;i++)
    for(int j=m;j>=v[i];j--){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }

实现

例题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次

第 i 件物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值

输出格式

输出一个整数,表示最大价值

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5

1 2

2 4

3 4

4 5

输出样例

8

解答

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> v[i] >> w[i];
	//f[0][0~m]=0,一件物品都没有选,所以其最大价值为0
	for(int i=1;i<=n;i++)
		for (int j = 0;j <= m;j++) {
			f[i][j] = f[i - 1][j];
			if (j >= v[i])
				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
		}
	cout << f[n][m] << endl;
	return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> 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]);
		}
	cout << f[m] << endl;
	return 0;
}

完全背包

问题描述

有 n 个物品和一个容量为 v 的背包,每个物品有两个属性,一个是其体积 vi ,一个是其价值(权重) wi ,每件物品可用无限次(每件物品有无限个),将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大

Dp 思想

实现

for (int i = 1;i <= n;i++)
	for (int j = 0;j <= m;j++)
		for (int k = 0;k * v[i] <= j;k++) //k不可以无限大,k个物品的总体积要小于等于j
			f[i][j] = max(f[i - 1][j], f[i - 1][j - k * v[i]] + w[i] * k);

优化

f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,f[i-1,j-3v]+3w,...)

f[i,j-v]=max( f[i-1,j-v] ,f[i-1,j-2v]+w ,f[i-1,j-3v]+2w,...)

粉色部分的最大值就是 f[i,j-v]+w

f[i,j]=max(f[i-1,j],f[i,j-v]+w)——>只枚举两个状态就可以,不需要枚举 k 个状态

for (int i = 1;i <= n;i++)
	for (int j = 0;j <= m;j++) {
		f[i][j] = f[i - 1][j];
		if(j>=v[i])
			f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
	}
for (int i = 1;i <= n;i++)
	for (int j = v[i];j <=m;j++) {//因为f[i][j]是从f[i][j-v[i]]优化得到的
                                  //计算第i层数据的时候,并不需要第i-1层数据
                                  //所以j直接从小到大遍历即可
		f[j] = max(f[j], f[j - v[i]] + w[i]);
		//f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
	}

例题

有 N种物品和一个容量是 V 的背包,每种物品都有无限件可用

第 i 种物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大

输出最大价值

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积

接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 件物品的体积和价值

输出格式

输出一个整数,表示最大价值。

据范围

0<N,V≤1000

0<vi,wi≤1000

输入样例

4 5

1 2

2 4

3 4

4 5

输出样例

10

解答

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> v[i] >> w[i];
	for (int i = 1;i <= n;i++)
		for (int j = 0;j <= m;j++)
			for (int k = 0;k * v[i] <= j;k++) //k不可以无限大,k个物品的总体积要小于等于j
				f[i][j] = max(f[i - 1][j], f[i - 1][j - k * v[i]] + w[i] * k);
	cout << f[n][m] << endl;
	return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> v[i] >> w[i];
	for (int i = 1;i <= n;i++)
		for (int j = 0;j <= m;j++) {
			f[i][j] = f[i - 1][j];
			if(j>=v[i])
				f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
		}
	cout << f[n][m] << endl;
	return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> 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]);
		}
	cout << f[m] << endl;
	return 0;
}

多重背包

问题描述

有 n 个物品和一个容量为 v 的背包,每个物品有三个属性,一个是其体积 vi ,一个是其价值(权重) wi ,还有一个是物品的个数 si ,将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大

Dp 思想

f[i][j]=max(f[i-1][j-v[i]*k]+w[i]*k);k=0,1,2,...,s[i]

实现

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

优化

完全背包的优化方式——不可行

f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,f[i-1,j-3v]+3w,...,f[i-1,j-sv]+sw)

f[i,j-v]=max( f[i-1,j-v] ,f[i-1,j-2v]+w ,f[i-1,j-3v]+2w,...,f[i-1,j-sv]+(s-1)w,f[i-1,j-(s+1)v]+sw)

给出 f[i,j-v] 的最大值,并不能求出来粉色部分的最大值

二进制优化方式

假设有 s 个第 i 种物品,是否需要逐个枚举第 i 种物品的个数 0~s?

是不需要逐个枚举第 i 种物品的个数

可以将对这些物品进行分组,分为 k 组,每组中第 i 种物品的个数依次为 1 , 2 , 4 , 8 , ... 2k ,利用这 k 组去凑 s

使用 1 可以凑出 0~1

使用 1、2 可以凑出 0~3

使用 1 、2、4 可以凑出 0~7

...

每一组最多只能选一次,将多重背包问题转换成 0 1 背包问题

s=1023

1

0~1

1、2

0~3

(0~1 +2—>2~3,加上之前的 0~1,变成 0~3)

1、2、4

0~7

(0~3 +4—>4~7,加上之前的 0~3,变成 0~7)

...

1、2、4、...、512

0~1023

s=200

1

0~1

1、2

0~3

(0~1 +2—>2~3,加上之前的 0~1,变成 0~3)

1、2、4

0~7

(0~3 +4—>4~7,加上之前的 0~3,变成 0~7)

...

1、2、4、...、64

0~217

1、2、4、...、64、128

0~255(只有200个第 i 种物品,凑不出 201~255)

1、2、4、...、64、73

0~217

(0~217 +73—>73~200,加上之前的 0~217,变成 0~200)

如何凑?

第 i 种物品,有 s 个

1、2、4、8、...、2k 、c

c < 2k+1

使用 1、2、4、8、...、2k ,可以凑出 0~2k+1 -1,+c 之后就可以凑出 c~s ,一共可以凑出

0~s

若 c>2k+1 ,则 0~2k+1 -1 与 c~s 之间有空隙,无法凑出 0~s

故,c=s-(2k+1 -1)

二进制具体优化方式

将第 i 种的 s 个物品分成 log s 组 s[i]—>log s[i]

一共有 n 种物品,每种 s[i] 个,变成了一共有 log s[1]+log s[2]+... +log s[n] 个物品,对这些物品做 0 1 背包问题

int cnt = 0;//表示所有新物品的编号
	for (int i = 1;i <= n;i++) {
		int a, b, s;
		cin >> a >> b >> s;//当前物品的体积,价值,个数
		int k = 1;//从1开始分
		while (k <= s) {
			cnt++;
			v[cnt] = a * k;//新物品的体积
			w[cnt] = b * k;//新物品的价值
			s -= k;
			k *= 2;
		}
		if (s > 0) {//剩余的那部分,即 c
			cnt++;
			v[cnt] = a * s;
			w[cnt] = b * s;
		}
	}
	n = cnt;//更新物品个数
	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]);

例题

例题1——数据范围小

有 N 种物品和一个容量是 V 的背包

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大
输出最大价值

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量

输出格式

输出一个整数,表示最大价值

数据范围

0<N,V≤100
0<vi,wi,si≤100

输入样例

4 5

1 2 3

2 4 1

3 4 3

4 5 2

输出样例

10

解答

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> v[i] >> w[i] >> s[i];
	for (int i = 1;i <= n;i++)
		for (int j = 0;j <= m;j++)
			for (int k = 0;k * v[i] <= j && k <= s[i];k++)
				f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i] * k);
	cout << f[n][m] << endl;
	return 0;
}

例题2——数据范围大

有 N种物品和一个容量是 V 的背用

i 种物品最多有 si 件,每件体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大

输出最大价值

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积

接下来有 N 行,每行两个整数 vi, wi,si 用空格隔开,分别表示第 i 件物品的体积和价值和数量

输出格式

输出一个整数,表示最大价值

数据范围

0<N≤1000

0<V≤2000

0<vi,wi,si≤2000

输入样例

4 5

1 2 3

2 4 1

3 4 3

4 5 2

输出样例

10

解答

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 15000, M = 2010;//物品个数,1000*log 2000=12000
int n, m;
int v[N], w[N];
int f[N];
int main() {
	cin >> n >> m;
	int cnt = 0;//表示所有新物品的编号
	for (int i = 1;i <= n;i++) {
		int a, b, s;
		cin >> a >> b >> s;//当前物品的体积,价值,个数
		int k = 1;//从1开始分
		while (k <= s) {
			cnt++;
			v[cnt] = a * k;//新物品的体积
			w[cnt] = b * k;//新物品的价值
			s -= k;
			k *= 2;
		}
		if (s > 0) {//剩余的那部分,即 c
			cnt++;
			v[cnt] = a * s;
			w[cnt] = b * s;
		}
	}
	n = cnt;//更新物品个数
	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]);
	cout << f[m] << endl;
	return 0;
}

分组背包

问题描述

有 n 组物品和一个容量为 v 的背包,每组物品有若干个,每组最多只能选一个物品,将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大

Dp 思想

实现

for (int i = 1;i <= n;i++)//从前往后枚举每一组物品
	for (int j = m;j >= 0;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]);

例题

有 N 组物品和一个容量是 V 的背包

每组物品有若干个,同一组内的物品最多只能选一个
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大

输出最大价值

输入格式

第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量

接下来有 N 组数据:

  • 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
  • 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输出格式

输出一个整数,表示最大价值

数据范围

0<N,V≤100
0<Si≤100
0<vij,wij≤100

输入样例

3 5

2

1 2

2 4

1

3 4

1

4 5

输出样例

8

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];//s存每组物品的个数
int f[N];
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++) {
		cin >> s[i];
		for (int j = 0;j < s[i];j++)
			cin >> v[i][j] >> w[i][j];
	}
	for (int i = 1;i <= n;i++)//从前往后枚举每一组物品
		for (int j = m;j >= 0;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]);
	cout << f[m] << endl;
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

何hyy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值