动态规划之背包问题的一些基础简单入门题

前言

参考视频教程洛谷试练场 普及组 动态规划的背包问题

在这里插入图片描述

主要有01背包问题、完全背包问题、分组背包问题。

01背包问题一般从右往左推;

完全背包问题一般从左往右推;

分组背包一般用01的方法但需要记得分组;

还是需要慢慢理解。

以简单基础普及题型为例,可在洛谷上查找题目提交,代码仅供参考。

题目列表:

1.P1048 [NOIP2005 普及组] 采药

2.P1060 [NOIP2006 普及组] 开心的金明

3.P1049 [NOIP2001 普及组] 装箱问题

4.P1164 小A点菜

5.P1734 最大约数和

6.P1510 精卫填海

7.P1466 [USACO2.2]集合 Subset Sums

8.P1616 疯狂的采药

9.P1679 神奇的四次方数

10.P1757 通天之分组背包

11.P1064 [NOIP2006 提高组] 金明的预算方案

1.P1048 [NOIP2005 普及组] 采药

题目描述

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

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

输入格式

第一行有 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。

代码

#include <iostream>

using namespace std;
int t, m, tt[105], money[105], dp[1005];

int main()
{
	cin >> t >> m;
	for (int i = 1; i <= m; i++)
	{
		cin >> tt[i] >> money[i];
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = t; j >= tt[i]; j--)
		{
			dp[j] = max(dp[j - tt[i]] + money[i], dp[j]);
		}
	}
	cout << dp[t];
}

2.P1060 [NOIP2006 普及组] 开心的金明

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NN元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的NN元。于是,他把每件物品规定了一个重要度,分为55等:用整数1-51−5表示,第55等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过NN元(可以等于NN元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第jj件物品的价格为v[j]v[j],重要度为w[j]w[j],共选中了kk件物品,编号依次为j_1,j_2,…,j_kj1,j2,…,j**k,则所求的总和为:

v[j_1] \times w[j_1]+v[j_2] \times w[j_2]+ …+v[j_k] \times w[j_k]v[j1]×w[j1]+v[j2]×w[j2]+…+v[j**kw[j**k]。

请你帮助金明设计一个满足要求的购物单。

输入格式

第一行,为22个正整数,用一个空格隔开:n,mn,m(其中N(<30000)N(<30000)表示总钱数,m(<25)m(<25)为希望购买物品的个数。)

从第22行到第m+1m+1行,第jj行给出了编号为j-1j−1的物品的基本数据,每行有22个非负整数v pv**p(其中vv表示该物品的价格(v \le 10000)(v≤10000),pp表示该物品的重要度(1-51−5)

输出格式

11个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)(<100000000)。

输入输出样例

输入 #1复制

1000 5
800 2
400 5
300 5
400 3
200 2

输出 #1复制

3900

代码

#include <iostream>

using namespace std;
int n, m, v[30], w[30], dp[30010];

int main()
{
	cin >> n >> m;        //跟上面一题同样思路
	for (int i = 1; i <= m; i++)
	{
		cin >> v[i] >> w[i];
	}
	for (int i = 1; i <= m; i++)
	{
		for (int t = n; t >= v[i]; t--)
		{
			dp[t] = max(dp[t - v[i]] + v[i] * w[i], dp[t]);
		}
	}
	cout << dp[n];
}

3.P1049 [NOIP2001 普及组] 装箱问题

题目描述

有一个箱子容量为VV(正整数,0 \le V \le 200000≤V≤20000),同时有nn个物品(0<n \le 300<n≤30,每个物品有一个体积(正整数)。

要求nn个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入格式

11个整数,表示箱子容量

11个整数,表示有nn个物品

接下来nn行,分别表示这nn个物品的各自体积

输出格式

11个整数,表示箱子剩余空间。

输入输出样例

输入 #1复制

24
6
8
3
12
7
9
7

输出 #1复制

0

代码

#include <iostream>

using namespace std;
int v, n, t[35], dp[20020];
//int vis[35];
//int maxx;

//void dfs(int sum)
//{
//	for(int i=1;i<=n;i++)
//	{
//		if(vis[i]==1)
//		{
//			continue;
//		}else
//		{
//			vis[i]=1;
//			if(sum+t[i]<=v)
//			{
//				maxx = max(maxx,sum+t[i]);
//			}else
//			{
//				vis[i]=0;
//				continue;
//			}
//			dfs(sum+t[i]);
//			vis[i]=0;
//		}
//	}
//}

int main()
{
	cin >> v;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> t[i];
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = v; j >= t[i]; j--)
		{
			dp[j] = max(dp[j - t[i]] + t[i], dp[j]);
			if (dp[j] == v)
			{
				cout << "0";
				return 0;
			}
		}
	}
	cout << v - dp[v];
	return 0;
	//	for(int i=1;i<=n;i++)   //尝试用递归的方法做,超时 
	//	{
	//		vis[i]=1;
	//		dfs(t[i]);
	//		vis[i]=0;
	//	}
	//	cout<<v-maxx;
}

4.P1164 小A点菜

题目背景

uim神犇拿到了uoira(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种。

uim指着墙上的价目表(太低级了没有菜单),说:“随便点”。

题目描述

不过uim由于买了一些辅(e)辅(ro)书,口袋里只剩MM元(M \le 10000)(M≤10000)。

餐馆虽低端,但是菜品种类不少,有NN种(N \le 100)(N≤100),第ii种卖a_ia**i元(a_i \le 1000)(a**i≤1000)。由于是很低端的餐馆,所以每种菜只有一份。

小A奉行“不把钱吃光不罢休”,所以他点单一定刚好吧uim身上所有钱花完。他想知道有多少种点菜方法。

由于小A肚子太饿,所以最多只能等待11秒。

输入格式

第一行是两个数字,表示NN和MM

第二行起NN个正数a_ia**i(可以有相同的数字,每个数字均在10001000以内)。

输出格式

一个正整数,表示点菜方案数,保证答案的范围在intint之内。

输入输出样例

输入 #1复制

4 4
1 1 2 2

输出 #1复制

3

代码

#include <iostream>

using namespace std;   //这题理解不透彻 
int n, m, a[1010], dp[10010][10010];
int dpp[10010] = { 1 };   //方案数肯定有一个 ,但我另一个方法又可以过,有点懵
int sum;
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= a[i]; j--)
		{
			dpp[j] = dpp[j] + dpp[j - a[i]];   //有点难理解,就是方案数等于当前方案数加上剩下的钱的方案数 
		}
	}
	//	for(int i=1;i<=n;i++)
	//	{
	//		for(int j=1;j<=m;j++)
	//		{
	//			if(a[i]<j) //当前菜品价格小于花费钱数 ,则可以方案数加上剩下钱的方案数 
	//			dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];
	//			if(a[i]==j)  //当前菜品价格等于花费钱数 ,则方案加一 
	//			dp[i][j]=dp[i-1][j]+1;
	//			if(a[i]>j) //当前菜品价格等于花费钱数 ,则方案不变 
	//			dp[i][j]=dp[i-1][j];
	//		}
	//	}
	//	cout<<dp[n][m];
	cout << dpp[m];
}

5.P1734 最大约数和

题目描述

选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。

image-20210411171900386

输入格式

输入一个正整数S。

输出格式

输出最大的约数之和。

输入输出样例

输入 #1复制

11

输出 #1复制

9

说明/提示

样例说明

取数字4和6,可以得到最大值(1+2)+(1+2+3)=9。

数据规模

S<=1000

代码

#include <iostream>

using namespace std;

int s, v[1010], dp[1010];

int main()
{
	cin >> s;
	for (int i = 1; i <= s; i++)
	{
		for (int j = 1; j < i; j++)
		{
			if (i % j == 0)
			{
				v[i] += j;
			}
		}
	}
	for (int i = 1; i <= s; i++)
	{
		for (int j = s; j >= i; j--)
		{
			dp[j] = max(dp[j], dp[j - i] + v[i]);   //突然就不是很理解了  2021.4.11 17:12 
		}
	}
	cout << dp[s];
}

6.P1510 精卫填海

【问题描述】

发鸠之山,其上多柘木。有鸟焉,其状如乌,文首,白喙,赤足,名曰精卫,其名自詨。是炎帝之少女,名曰女娃。女娃游于东海,溺而不返,故为精卫。常衔西山之木石,以堙于东海。——《山海经》

精卫终于快把东海填平了!只剩下了最后的一小片区域了。同时,西山上的木石也已经不多了。精卫能把东海填平吗?

事实上,东海未填平的区域还需要至少体积为v的木石才可以填平,而西山上的木石还剩下n块,每块的体积和把它衔到东海需要的体力分别为k和m。精卫已经填海填了这么长时间了,她也很累了,她还剩下的体力为c。

输入格式

输入文件的第一行是三个整数:v、n、c。

从第二行到第n+1行分别为每块木石的体积和把它衔到东海需要的体力。

输出格式

输出文件只有一行,如果精卫能把东海填平,则输出她把东海填平后剩下的最大的体力,否则输出’Impossible’(不带引号)。

输入输出样例

输入 #1复制

100 2 10
50 5
50 5

输出 #1复制

0

输入 #2复制

10 2 1
50 5
10 2

输出 #2复制

Impossible

说明/提示

【数据范围】

对于20%的数据,0<n<=50。

对于50%的数据,0<n<=1000。

对于100%的数据,0<n<=10000,所有读入的数均属于[0,10000],最后结果<=c。

代码

#include <iostream>

using namespace std;
int v, n, c, tj[10010], li[10010], dp[100101];

int main()
{
	cin >> v >> n >> c;
	for (int i = 1; i <= n; i++)
	{
		cin >> tj[i] >> li[i];
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = c; j >= li[i]; j--)    //以 力 为衡量
		{
			dp[j] = max(dp[j], dp[j - li[i]] + tj[i]);
		}
	}
	for (int i = 1; i <= c; i++)
	{
		if (dp[i] >= v)
		{
			cout << c - i;
			return 0;
		}
	}
	cout << "Impossible";
	return 0;
}

7.P1466 [USACO2.2]集合 Subset Sums

题目描述

对于从 1\sim n1∼n 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。举个例子,如果 n=3n=3,对于 {1,2,3}{1,2,3} 能划分成两个子集合,每个子集合的所有数字和是相等的:

{3}{3} 和 {1,2}{1,2} 是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果 n=7n=7,有四种方法能划分集合 {1,2,3,4,5,6,7 }{1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的:

{1,6,7}{1,6,7} 和 {2,3,4,5}{2,3,4,5}
{2,5,7}{2,5,7} 和 {1,3,4,6}{1,3,4,6}
{3,4,7}{3,4,7} 和 {1,2,5,6}{1,2,5,6}
{1,2,4,7}{1,2,4,7} 和 {3,5,6}{3,5,6}

给出 nn,你的程序应该输出划分方案总数。

输入格式

输入文件只有一行,且只有一个整数 nn

输出格式

输出划分方案总数。

输入输出样例

输入 #1复制

7

输出 #1复制

4

说明/提示

【数据范围】
对于 100%100% 的数据,1\le n \le 391≤n≤39。

代码

#include <iostream>

using namespace std;

long long  dp[10010] = { 1 };  //注意默认有一个方案 

int main()
{
	int n;
	cin >> n;
	int m = (1 + n) * n / 2;
	if (m % 2 == 1)
	{
		cout << "0";
		return 0;
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = m / 2; j >= i; j--)
		{
			dp[j] = dp[j] + dp[j - i];
		}
	}
	cout << dp[m / 2] / 2;
	return 0;
}

8.P1616 疯狂的采药

题目描述

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

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

此题和原题的不同点:

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

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

输入格式

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

第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_ia**i,b**i 分别表示采摘第 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≤a**i,b**i≤104。

代码

#include <iostream>

using namespace std;  //完全背包问题 
long long t, m, a[1000010], b[1000010], dp[10000010];

int main()
{
	cin >> t >> m;
	for (long long i = 1; i <= m; i++)
	{
		cin >> a[i] >> b[i];
	}
	for (long long i = 1; i <= m; i++)
	{
		for (long long j = a[i]; j <= t; j++)   //完全背包从左往右 
		{
			dp[j] = max(dp[j], dp[j - a[i]] + b[i]);
		}
	}
	cout << dp[t];
	return 0;
}

9.P1679 神奇的四次方数

题目描述

在你的帮助下,v神终于帮同学找到了最合适的大学,接下来就要通知同学了。在班级里负责联络网的是dm同学,于是v神便找到了dm同学,可dm同学正在忙于研究一道有趣的数学题,为了请dm出山,v神只好请你帮忙解决这道题了。

题目描述:将一个整数m分解为n个四次方数的和的形式,要求n最小。例如,m=706,706=54+34,则n=2。

输入格式

一行,一个整数m。

输出格式

一行,一个整数n。

输入输出样例

输入 #1复制

706

输出 #1复制

2

说明/提示

数据范围:对于30%的数据,m<=5000;对于100%的数据,m<=100,000

代码

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int m, dp[100010], a[100010];
int main()
{
	cin >> m;
	int cnt;
	memset(dp, 60, sizeof(dp));
	dp[0] = 0;
	for (int i = 1; i <= m; i++)
	{
		a[i] = pow(i, 4);
		if (a[i] > m)
		{
			cnt = i;
			break;
		}
	}
	for (int i = 1; i <= cnt; i++)
	{
		for (int j = a[i]; j <= m; j++)
		{
			dp[j] = min(dp[j], dp[j - a[i]] + 1);
		}
	}
	cout << dp[m];
	return 0;
}

10.P1757 通天之分组背包

题目描述

自 0101 背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于 0101 背包,他的物品大致可分为 kk 组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入格式

两个数 m,nm,n,表示一共有 nn 件物品,总重量为 mm

接下来 nn 行,每行 33 个数 a_i,b_i,c_ia**i,b**i,c**i,表示物品的重量,利用价值,所属组数。

输出格式

一个数,最大的利用价值。

输入输出样例

输入 #1复制

45 3
10 10 1
10 5 1
50 400 2

输出 #1复制

10

说明/提示

1 \leq m, n \leq 10001≤m,n≤1000。

代码

#include <iostream>

using namespace std;
int m, n, dp[1010];
struct bag
{
	int tat;
	int w[1010];
	int v[1010];
}a[1010];

int main()
{
	cin >> m >> n;
	int cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		cnt = max(cnt, z);  //组数 
		a[z].tat++;      //组内的数量 
		a[z].w[a[z].tat] = x;
		a[z].v[a[z].tat] = y;
	}
	for (int i = 1; i <= cnt; i++)
	{
		for (int j = m; j >= 0; j--)
		{
			for (int k = 1; k <= a[i].tat; k++)
			{
				if (j - a[i].w[k] >= 0)
					dp[j] = max(dp[j], dp[j - a[i].w[k]] + a[i].v[k]);
			}
		}
	}
	cout << dp[m];
	return 0;
}

11.P1064 [NOIP2006 提高组] 金明的预算方案

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 nn 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件附件
电脑打印机,扫描仪
书柜图书
书桌台灯,文具
工作椅

如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 00 个、11 个或 22 个附件。每个附件对应一个主件,附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 nn 元。于是,他把每件物品规定了一个重要度,分为 55 等:用整数 1 \sim 51∼5 表示,第 55 等最重要。他还从因特网上查到了每件物品的价格(都是 1010 元的整数倍)。他希望在不超过 nn 元的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第 jj 件物品的价格为 v_jv**j,重要度为w_jw**j,共选中了 kk 件物品,编号依次为 j_1,j_2,\dots,j_kj1,j2,…,j**k,则所求的总和为:

v_{j_1} \times w_{j_1}+v_{j_2} \times w_{j_2}+ \dots +v_{j_k} \times w_{j_k}v**jw**j1+v**jw**j2+⋯+vjk×wjk

请你帮助金明设计一个满足要求的购物单。

输入格式

第一行有两个整数,分别表示总钱数 nn 和希望购买的物品个数 mm

第 22 到第 (m + 1)(m+1) 行,每行三个整数,第 (i + 1)(i+1) 行的整数 v_iv**i,p_ip**i,q_iq**i 分别表示第 ii 件物品的价格、重要度以及它对应的的主件。如果 q_i=0q**i=0,表示该物品本身是主件。

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1复制

1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0

输出 #1复制

2200

说明/提示

数据规模与约定

对于全部的测试点,保证 1 \leq n \leq 3.2 \times 10^41≤n≤3.2×104,1 \leq m \leq 601≤m≤60,0 \leq v_i \leq 10^40≤v**i≤104,1 \leq p_i \leq 51≤p**i≤5,0 \leq q_i \leq m0≤q**im,答案不超过 2 \times 10^52×105。

代码

#include <iostream>

using namespace std;
int n, m, dp[100010];

struct bag
{
	int tot;
	int p[5];  //总共有4种可能
	int v[5];
}a[100];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		if (z == 0)
		{
			a[i].tot = 1;
			a[i].p[1] = x;
			a[i].v[1] = x * y;
		}
		else
		{
			if (a[z].tot == 1)
			{
				a[z].p[2] = a[z].p[1] + x;    //因为题目说最多两个附件,一个个放进去后面再dp的时候算最优的就行了 
				a[z].v[2] = a[z].v[1] + x * y;
				a[z].tot = 2;
			}
			else
			{
				a[z].p[3] = a[z].p[1] + x;
				a[z].v[3] = a[z].v[1] + x * y;
				a[z].p[4] = a[z].p[2] + x;
				a[z].v[4] = a[z].v[2] + x * y;
				a[z].tot = 4;
			}
		}
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = n; j >= 0; j--)
		{
			for (int k = 1; k <= a[i].tot; k++)  //分组背包噢 
			{
				if (j - a[i].p[k] >= 0)     //这个别忘了 
					dp[j] = max(dp[j], dp[j - a[i].p[k]] + a[i].v[k]);
			}
		}
	}
	cout << dp[n];
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值