前言
参考视频教程洛谷试练场 普及组 动态规划的背包问题
主要有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**k]×w[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
神犇拿到了uoi
的ra
(镭牌)后,立刻拉着基友小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的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。
输入格式
输入一个正整数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,你能完成这个任务吗?
此题和原题的不同点:
-
每种草药可以无限制地疯狂采摘。
-
药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!
输入格式
输入第一行有两个整数,分别代表总共能够用来采药的时间 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**j1×w**j1+v**j2×w**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**i≤m,答案不超过 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;
}