动态规划——背包问题(个别)

一、01背包问题

题目链接:2. 01背包问题 - AcWing题库

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

第 ii 件物品的体积是 vivi,价值是 wiwi。

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

输入格式

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

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

输出格式

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

数据范围

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

   f[i][j]代表的是前i件物品,总体积不超过j的总价值

     将问题的状态转移分为两种:

        1)不选第i个物品  f [i] [j] = f [i-1] [j]; 
        2)选第i个物品     f [i] [j] = f [i-1] [j-v[i]] + w [i];

代码部分:

        首先是使用二维数组的做法。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005][1005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&v[i],&w[i]);
	f[0][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]);
			}
		}
	printf("%d",f[n][m]);
	return 0;
}

        此时会发现其实数组中的i维是可以通过改写省去的,就有了如下的一维写法。

​
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&v[i],&w[i]);
	f[0]=0;
	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",f[m]);
	return 0;
}

​

这里还有几道01背包问题的题目:

P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1048

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目。

接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

3

说明/提示

【数据范围】

  • 对于 30\%30% 的数据,M \le 10M≤10;
  • 对于全部的数据,M \le 100M≤100。

【题目来源】

NOIP 2005 普及组第三题

 附上AC代码:

#include<bits/stdc++.h>
using namespace std;
long long int t,m;
long long int v[1005],w[1005];
long long int f[1005][1005];
int main()
{
	scanf("%lld%lld",&t,&m);
	for(int i=1;i<=m;i++)
		scanf("%lld%lld",&v[i],&w[i]);
	f[0][0]=0;
	for(int i=1;i<=m;i++)
		for(int j=0;j<=t;j++)
		{
			f[i][j]=f[i-1][j];
			if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
		}
	printf("%lld",f[m][t]);
	return 0;
}

 P1802 5 倍经验日 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1802

题目背景

现在乐斗有活动了!每打一个人可以获得 5 倍经验!absi2011 却无奈的看着那一些比他等级高的好友,想着能否把他们干掉。干掉能拿不少经验的。

题目描述

现在 absi2011 拿出了 xx 个迷你装药物(嗑药打人可耻…),准备开始与那些人打了。

由于迷你装药物每个只能用一次,所以 absi2011 要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 22 个药去打别人,别人却表明 33 个药才能打过,那么相当于你输了并且这两个属性药浪费了。

现在有 nn 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。

要求求出最大经验 ss,输出 5s5s。

输入格式

第一行两个数,nn 和 xx。

后面 nn 行每行三个数,分别表示失败时获得的经验 \mathit{lose}_ilosei​,胜利时获得的经验 \mathit{win}_iwini​ 和打过要至少使用的药数量 \mathit{use}_iusei​。

输出格式

一个整数,最多获得的经验的五倍。

输入输出样例

输入 #1复制

6 8
21 52 1
21 70 5
21 48 2
14 38 3
14 36 1
14 36 2

输出 #1复制

1060

说明/提示

【Hint】

五倍经验活动的时候,absi2011 总是吃体力药水而不是这种属性药。

【数据范围】

  • 对于 10\%10% 的数据,保证 x=0x=0。
  • 对于 30\%30% 的数据,保证 0\le n\le 100≤n≤10,0\le x\le 200≤x≤20。
  • 对于 60\%60% 的数据,保证 0\le n,x\le 1000≤n,x≤100, 10<lose_i,win_i\le 10010<losei​,wini​≤100,0\le use_i\le 50≤usei​≤5。
  • 对于 100\%100% 的数据,保证 0\le n,x\le 10^30≤n,x≤103,0<lose_i\le win_i\le 10^60<losei​≤wini​≤106,0\le use_i\le 10^30≤usei​≤103。

【题目来源】

fight.pet.qq.com

absi2011 授权题目

这道是一道有些许变化的01背包问题,不难,附上AC代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1005;
long long int n,x;
long long int lose[N],win[N],use[N];
long long int f[N];
int main()
{
	scanf("%d%d",&n,&x);
	for(int i=1;i<=n;i++)
		scanf("%d%d%d",&lose[i],&win[i],&use[i]);
	for(int i=1;i<=n;i++)
		for(int j=x;j>=0;j--)
		{
			if(j>=use[i])
				f[j]=max(f[j]+lose[i],f[j-use[i]]+win[i]);
			else
				f[j]=f[j]+lose[i];
		}
	cout<<5ll*f[x];
	return 0;
}

二、完全背包问题

题目链接:3. 完全背包问题 - AcWing题库

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

第 ii 种物品的体积是 vivi,价值是 wiwi。

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

输入格式

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

接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。

输出格式

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

数据范围

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

 f[i][j]表示从前i个物品里选,总体积不超过j时,最大的价值

将问题的状态转移分为两种:

        1)不选第i个物品            f [i] [j] = f [i-1] [j]; 
        2)选不定个第i个物品     f [i] [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-1] [j-2*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] [j] = max ( f[ i-1] [j] , f [i] [j-v[i]] + w[i] )

#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005][1005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&v[i],&w[i]);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			f[i][j]=f[i-1][j];
			if(j>=v[i])
			{
				f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
			}
		}
	printf("%d",f[n][m]);
    return 0;
}


P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1616

题目背景

此题为纪念 LiYuxiang 而生。

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

11. 每种草药可以无限制地疯狂采摘。

22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm。

第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_iai​,bi​ 分别表示采摘第 ii 种草药的时间和该草药的价值。

输出格式

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

140

说明/提示

数据规模与约定

  • 对于 30\%30% 的数据,保证 m \le 10^3m≤103 。
  • 对于 100\%100% 的数据,保证 1 \leq m \le 10^41≤m≤104,1 \leq t \leq 10^71≤t≤107,且 1 \leq m \times t \leq 10^71≤m×t≤107,1 \leq a_i, b_i \leq 10^41≤ai​,bi​≤104。

这也是一道完全背包问题,几乎一样的做法,但是记得数组开大一点,不然会RE,附上AC代码:

#include<bits/stdc++.h>
using namespace std;
long long int t,m;
long long int v[10005],w[10005];
long long int f[10000005];
int main()
{
	scanf("%lld%lld",&t,&m);
	for(int i=1;i<=m;i++)
		scanf("%lld%lld",&v[i],&w[i]);
	f[0]=0;
	for(int i=1;i<=m;i++)
		for(int j=v[i];j<=t;j++)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	printf("%lld",f[t]);
	return 0;
}

三、多重背包问题(Ⅰ)

题目链接:4. 多重背包问题 I - AcWing题库

有 NN 种物品和一个容量是 VV 的背包。

第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。

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

输入格式

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

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

输出格式

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

数据范围

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

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

 多重背包不像完全背包一样可以无限地选取,所以不能推导出那样的一般式。所以直接从0开始循环到s[i]。

f[i][j]表示从第i个物品里选k个,总体积不超过j时,最大的价值

选取k件第i件物品 f [i] [j] = f [i-1] [j - v[i] * k] + w [i] * k;

#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005],s[1005];
int f[1005][1005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d%d",&v[i],&w[i],&s[i]);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=0;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-v[i]*k]+w[i]*k);
	cout<<f[n][m];
	return 0;
}

四、多重背包问题(Ⅱ)

题目链接:5. 多重背包问题 II - AcWing题库

有 NN 种物品和一个容量是 VV 的背包。

第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。

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

输入格式

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

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

输出格式

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

数据范围

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

 如果和Ⅰ一样三重循环,那就是10的9次方,肯定会超时,所以需要优化,这里采用的是二进制优化。将物品个数变成1,2,4……然后可以拼成1~s的任意个数,将多重背包问题转化为01背包问题。

O(n)=N*M*logs;

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=10000005;
int v[N],w[N],s[N];
int f[N];
int main()
{
	scanf("%d%d",&n,&m);
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		int a,b,c,k=1;
		scanf("%d%d%d",&a,&b,&c);
		while(k<=c)
		{
			cnt++;
			v[cnt]=a*k;
			w[cnt]=b*k;
			c-=k;
			k*=2;
		}
		if(c>0)
		{
			cnt++;
			v[cnt]=a*c;
			w[cnt]=b*c;
		}
	}
	for(int i=1;i<=cnt;i++)
		for(int j=m;j>=v[i];j--)
			f[j]=max(f[j-1],f[j-v[i]]+w[i]);
	cout<<f[m];
	
	return 0;
}

 五、分组背包问题

题目链接:9. 分组背包问题 - AcWing题库

有 NN 组物品和一个容量是 VV 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vijvij,价值是 wijwij,其中 ii 是组号,jj 是组内编号。

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

输出最大价值。

输入格式

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

接下来有 NN 组数据:

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

输出格式

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

数据范围

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

1.第i组物品不选  f[i][j]=f[i-1][j];
2.第i组物品选哪个 f[i][j]=max(f[i][j],f[i-1][j-v[i,k]]+w[i,k]

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N][N];
int main()
{
	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=0;j<=m;j++)
		{
			f[i][j]=f[i-1][j];
			for(int k=1;k<=s[i];k++)
			{
				if(j>=v[i][k])
					f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
			}
		}
	cout<<f[n][m];
}

优化成一维数组:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
int main()
{
	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>=0;j--)
			for(int k=1;k<=s[i];k++)
				if(j>=v[i][k])
					f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
	cout<<f[m];
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值