本文的题目以及解题思路均来自AcWing算法提高课。部分题目在AcWing中收费,收费的题目在首页 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)或者Home - HydroOJ中也能找到。题目大多数是复制粘贴的,副标题可能没有对齐。
采药
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
输入文件的第一行有两个整数 TT 和 MM,用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目。
接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
数据范围
1≤T≤10001≤T≤1000,
1≤M≤1001≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3
思路和代码
非常经典的01背包问题。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int main() {
cin >> m >> n;
for(int i = 1; i <= n; i ++ ) {
int v, w;
cin >> v >> w;
for(int j = m; j >= v; j -- ) {
f[j] = max(f[j], f[j - v] + w);
}
}
cout << f[m] << endl;
return 0;
}
装箱问题
[P1049 NOIP2001 普及组] 装箱问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
有一个箱子容量为 V V V,同时有 n n n 个物品,每个物品有一个体积。
现在从 n n n 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。
输入格式
第一行共一个整数 V V V,表示箱子容量。
第二行共一个整数 n n n,表示物品总数。
接下来 n n n 行,每行有一个正整数,表示第 i i i 个物品的体积。
输出格式
- 共一行一个整数,表示箱子最小剩余空间。
样例 #1
样例输入 #1
24
6
8
3
12
7
9
7
样例输出 #1
0
提示
对于 100 % 100\% 100% 数据,满足 0 < n ≤ 30 0<n \le 30 0<n≤30, 1 ≤ V ≤ 20000 1 \le V \le 20000 1≤V≤20000。
【题目来源】
NOIP 2001 普及组第四题
思路和代码
注意这一题的价值就是体积。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 20010;
int n, m;
int f[N];
int main() {
cin >> m >> n;
for(int i = 1; i <= n; i ++ ) {
int v;
cin >> v;
for(int j = m; j >= v; j -- ) {
f[j] = max(f[j], f[j - v] + v);
}
}
cout << m - f[m] << endl;
return 0;
}
宠物小精灵之收服
U266184 宠物小精灵之收服 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。小智也想收服其中的一些小精灵。然而,野生的小精灵并不那么容易被收服。对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N(0<N<1000),M(0<M<500),K(0<K<100),分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。
样例 #1
样例输入 #1
10 100 5
7 10
2 40
2 50
1 20
4 20
样例输出 #1
3 30
样例 #2
样例输入 #2
10 100 5
8 110
12 10
20 10
5 200
1 110
样例输出 #2
0 100
提示
对于样例输入1:小智选择:(7,10) (2,40) (1,20) 这样小智一共收服了3个小精灵,皮卡丘受到了70点伤害,剩余100-70=30点体力。所以输出3 30。
对于样例输入2:小智一个小精灵都没法收服,皮卡丘也不会收到任何伤害,所以输出0 100。
思路和代码
注意这道题有两个限制条件,多了一个维度。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010, M = 510;
int n, m, k;
int f[N][M];
int main() {
cin >> n >> m >> k;
for(int i = 1; i <= k; i ++ ) {
int v1, v2;
cin >> v1 >> v2;
for(int j = n; j >= v1; j --) {
for(int l = m; l >= v2; l -- ) {
f[j][l] = max(f[j][l], f[j - v1][l - v2] + 1);
}
}
}
int res;
for(int i = 1; i <= m; i ++ ) {
if(f[n][i] == f[n][m]) {
res = i;
break;
}
}
cout << f[n][m] << " " << m - res << endl;
return 0;
}
数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
答案保证在 int 范围内。
输入样例:
4 4
1 1 2 2
输出样例:
3
思路和代码
用f[i, j]表示前i个数字,和恰好为j的方案数。
可以将f[i, j]的来源分为:包括第i个数字和不包括第i个数字。
如果不包括第i个数字,那么f[i, j] = f[i - 1, j]
如果包括第i个数字,那么f[i, j] = f[i - 1, j - a]
所以f[i, j] = f[i - 1, j] + f[i - 1, j - a]
最后优化为一维数组
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10010;
int n, m;
int f[N]; //f[i]表示和为i的方案数
int main() {
int n;
cin >> n >> m;
f[0] = 1;
for(int i = 1; i <= n; i ++ ) {
int a;
cin >> a;
for(int j = m; j >= a; j -- ) {
f[j] = f[j] + f[j - a];
}
}
cout << f[m] << endl;
return 0;
}
买书
题目描述
小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
输入
一个整数 n,代表总共钱数。(0 ≤ n ≤ 1000)
输出
一个整数,代表选择方案种数。
样例
Sample Input 1
20
Sample Output 1
2
来源
一本通在线评测
思路和代码
这是一个完全背包问题。
f[i, j]表示前i个数字,和恰好为j的方案数。
f[i, j] = f[i, j - v] + f[i, j];
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n;
int a[4] = {10, 20, 50, 100};
int f[N];
int main() {
cin >> n;
f[0] = 1;
for(int i = 0; i < 4; i ++ ) {
for(int j = a[i]; j <= n; j ++ ) {
f[j] = f[j] + f[j - a[i]];
}
}
cout << f[n] << endl;
return 0;
}
货币系统
在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。
在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 xx 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。
两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m。
输入格式
输入文件的第一行包含一个整数 TT,表示数据的组数。
接下来按照如下格式分别给出 TT 组数据。
每组数据的第一行包含一个正整数 nn。
接下来一行包含 nn 个由空格隔开的正整数 a[i]a[i]。
输出格式
输出文件共有 TT 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a)(n,a) 等价的货币系统 (m,b)(m,b) 中,最小的 mm。
数据范围
1≤n≤1001≤n≤100,
1≤a[i]≤250001≤a[i]≤25000,
1≤T≤201≤T≤20
输入样例:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例:
2
5
思路与代码
这是一个完全背包问题。
用f[i, j]表示前i个数字的和恰好为j的组合的个数。
f[i, j] = f[i - 1, j] + f[i, j - a[i]]
要注意res不要最后才算,第一次遍历到a[i]的时候就要判断f[a[i]]是否为0,因为a[i]也会使f[a[i]]至少为1.
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 25010, M = 110;
int t, n;
int a[M];
int f[N];
int main() {
cin >> t;
while(t -- ) {
cin >> n;
for(int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
memset(f, 0, sizeof f);
f[0] = 1;
int res = 0;
for(int i = 1; i <= n; i ++ ) {
if(f[a[i]] == 0) res ++;
for(int j = a[i]; j <= a[n]; j ++ ) {
f[j] = f[j] + f[j - a[i]];
}
}
cout << res << endl;
}
return 0;
}
庆功会
【例9.13】庆功会 - Problem Detail - HydroOJ
题目描述
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
输入
第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v≤100,w≤1000,s≤10。
输出
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
样例
Sample Input 1
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
[Copy](javascript:😉
Sample Output 1
1040
[Copy](javascript:😉
来源
一本通在线评测
思路和代码
多重背包问题模板,我不会滑动窗口优化的多重背包问题,我只会二进制优化~
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int M = 6010, N = 5010;
int n, m;
int v[N], w[N];
int f[M];
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;
while(k <= s) {
cnt ++;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
if(s > 0) {
cnt ++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
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;
}
混合背包问题
有 NN 种物品和一个容量是 VV 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 sisi 次(多重背包);
每种体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
- si=−1si=−1 表示第 ii 种物品只能用1次;
- si=0si=0 表示第 ii 种物品可以用无限次;
- si>0si>0 表示第 ii 种物品可以使用 sisi 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
思路和代码
对于每一个物品,判断是属于哪一个类型,然后再直接用公式套。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) {
int v, w, s;
cin >> v >> w >> s;
if(s == 0) {
for(int j = w; j <= m; j ++ ) {
f[j] = max(f[j], f[j - v] + w);
}
}
else {
if(s == -1) {
s = 1;
}
for(int k = 1; k <= s; k *= 2) {
for(int j = m; j >= k * v; j -- ) {
f[j] = max(f[j], f[j - k * v] + k * w);
}
s -= k;
}
if(s) {
for(int j = m; j >= s * v; j -- ) {
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
}
}
cout << f[m] << endl;
return 0;
}
二维费用的背包问题
有 NN 件物品和一个容量是 VV 的背包,背包能承受的最大重量是 MM。
每件物品只能用一次。体积是 vivi,重量是 mimi,价值是 wiwi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数,N,V,MN,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 NN 行,每行三个整数 vi,mi,wivi,mi,wi,用空格隔开,分别表示第 ii 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V,M≤1000<V,M≤100
0<vi,mi≤1000<vi,mi≤100
0<wi≤10000<wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
思路和代码
多了一个维度,多一层循环即可。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
int n, V, M;
int f[N][N];
int main() {
cin >> n >> V >> M;
for(int i = 1; i <= n; i ++ ) {
int v, m, w;
cin >> v >> m >> w;
for(int j = V; j >= v; j -- ) {
for(int k = M; k >= m; k -- ) {
f[j][k] = max(f[j][k], f[j - v][k - m] + w);
}
}
}
cout << f[V][M] << endl;
return 0;
}
潜水员
题目描述
潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入
第一行有2整数m,n(1≤m≤21,1≤n≤79)。它们表示氧,氮各自需要的量。
第二行为整数k(1≤n≤1000)表示气缸的个数。
此后的k行,每行包括a_i,b_i,c_i(1≤a_i≤21,1≤b_i≤79,1≤c_i≤800)3ai,bi,ci(1≤ai≤21,1≤bi≤79,1≤ci≤800)3整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
输出
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
样例
Sample Input 1
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
Sample Output 1
249
来源
一本通在线评测
思路和代码
f[k, i, j]表示前k个物品,氧气至少为i,氮气至少为j,的重量的最小值
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 85, M = 25;
int f[M][N];
int n, m, k;
int main() {
cin >> m >> n >> k;
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int l = 1; l <= k; l ++ ) {
int a, b, c;
cin >> a >> b >> c;
for(int i = m; i >= 0; i -- ) {
for(int j = n; j >= 0; j -- ) {
f[i][j] = min(f[i][j], f[max(i - a, 0)][max(j - b, 0)] + c);
}
}
}
cout << f[m][n] << endl;
return 0;
}
机器分配
P2066 机器分配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
机器分配
题目描述
总公司拥有高效设备 M M M 台,准备分给下属的 N N N 个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这 M M M 台设备才能使国家得到的盈利最大?求出最大盈利值。其中 M ≤ 15 M \le 15 M≤15, N ≤ 10 N \le 10 N≤10。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M M M。
输入格式
第一行有两个数,第一个数是分公司数 N N N,第二个数是设备台数 M M M。
接下来是一个 N × M N \times M N×M 的矩阵,表明了第 i i i 个公司分配 j j j 台机器的盈利。
输出格式
第一行为最大盈利值。
接下来 N N N 行为第 i i i 分公司分 x x x 台。
P.S. 要求答案的字典序最小。
样例 #1
样例输入 #1
3 3
30 40 50
20 30 50
20 25 30
样例输出 #1
70
1 1
2 1
3 1
思路及代码
每个小公司看成一个分组,每个分组有体积为0 ~ m的物品,在每个分组中选择一个物品,使得总体积不超过m。
设f[i, j]为在前i个分组中,总体积不超过j的盈利的最大值。分组背包问题依次遍历每个分组、每个体积、每个决策。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int w[N][N];
int f[N];
int way[N];
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= m; j ++ ) {
cin >> w[i][j];
}
}
for(int i = 1; i <= n; i ++ ) { //循环每一组物品
for(int j = m; j >= 0; j -- ) { //循环每一个体积
for(int k = 0; k <= j; k ++ ) { // 循环每一个决策
f[j] = max(f[j], f[j - k] + w[i][k]);
}
}
}
cout << f[m] << endl;
int j = m;
for(int i = n; i >= 1; i -- ) {
for(int k = j; k >= 0; k -- ) {
if(f[j] == f[j - k] + w[i][k]) {
j -= k;
way[i] = k;
break;
}
}
}
for(int i = 1; i <= n; i ++ ) cout << i << " " << way[i] << endl;
return 0;
}
开心的金明
[P1064 NOIP2006 提高组] 金明的预算方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 n n n 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
---|---|
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 0 0 个、 1 1 1 个或 2 2 2 个附件。每个附件对应一个主件,附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 n n n 元。于是,他把每件物品规定了一个重要度,分为 5 5 5 等:用整数 1 ∼ 5 1 \sim 5 1∼5 表示,第 5 5 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 10 10 元的整数倍)。他希望在不超过 n n n 元的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j j j 件物品的价格为 v j v_j vj,重要度为 w j w_j wj,共选中了 k k k 件物品,编号依次为 j 1 , j 2 , … , j k j_1,j_2,\dots,j_k j1,j2,…,jk,则所求的总和为:
v j 1 × w j 1 + v j 2 × w j 2 + ⋯ + v j k × w j k v_{j_1} \times w_{j_1}+v_{j_2} \times w_{j_2}+ \dots +v_{j_k} \times w_{j_k} vj1×wj1+vj2×wj2+⋯+vjk×wjk
请你帮助金明设计一个满足要求的购物单。
输入格式
第一行有两个整数,分别表示总钱数 n n n 和希望购买的物品个数 m m m。
第 2 2 2 到第 ( m + 1 ) (m + 1) (m+1) 行,每行三个整数,第 ( i + 1 ) (i + 1) (i+1) 行的整数 v i v_i vi, p i p_i pi, q i q_i qi 分别表示第 i i i 件物品的价格、重要度以及它对应的的主件。如果 q i = 0 q_i=0 qi=0,表示该物品本身是主件。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
样例输出 #1
2200
提示
数据规模与约定
对于全部的测试点,保证 1 ≤ n ≤ 3.2 × 1 0 4 1 \leq n \leq 3.2 \times 10^4 1≤n≤3.2×104, 1 ≤ m ≤ 60 1 \leq m \leq 60 1≤m≤60, 0 ≤ v i ≤ 1 0 4 0 \leq v_i \leq 10^4 0≤vi≤104, 1 ≤ p i ≤ 5 1 \leq p_i \leq 5 1≤pi≤5, 0 ≤ q i ≤ m 0 \leq q_i \leq m 0≤qi≤m,答案不超过 2 × 1 0 5 2 \times 10^5 2×105。
NOIP 2006 提高组 第二题
思路和代码
非常难
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 65, M = 32010;
typedef pair<int, int> PII;
PII a[N];
vector<PII> b[N];
int n, m;
int f[M];
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i ++ ) {
int v, p, q;
cin >> v >> p >> q;
if(!q) a[i] = {v, v * p};
else b[q].push_back({v, v * p});
}
for(int i = 1; i <= m; i ++ ) {
if(a[i].first) {
for(int j = n; j >= 0; j -- ) {
for(int k = 0; k < 1 << b[i].size(); k ++ ) {
int v = a[i].first, w = a[i].second;
for(int u = 0; u < b[i].size(); u ++ ) {
if(k >> u & 1) {
v += b[i][u].first;
w += b[i][u].second;
}
}
if(j >= v) f[j] = max(f[j], f[j - v] + w);
}
}
}
}
cout << f[n] << endl;
return 0;
}
有依赖的背包问题
有 N 个物品和一个容量是 V 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 N 行数据,每行数据表示一个物品。
第 ii 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
数据范围
1≤N,V≤10
1≤vi,wi≤100
父节点编号范围:
- 内部结点:1≤pi≤N;
- 根节点 pi=−1;
输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2
输出样例:
11
思路和代码
f[i, j]设为以i为根节点,体积不超过j的集合中,物品价值的最大值。
问题可以视为分组背包问题+DFS
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][N];
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void dfs(int u) {
for(int i = h[u]; i != -1; i = ne[i]) { //循环物品组
int son = e[i];
dfs(e[i]);
//分组背包
for(int j = m - v[u]; j >= 0; j -- ) { //循环体积
for(int k = 0; k <= j; k ++ ) { //循环决策
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
//将物品u加进去
for(int i = m; i >= v[u]; i -- ) f[u][i] = f[u][i - v[u]] + w[u]; //此处只能从大到小遍历,不能从小到大,为什么
for(int i = 0; i < v[u]; i ++ ) f[u][i] = 0;
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
int root;
for(int i = 1; i <= n; i ++ ) {
int p;
cin >> v[i] >> w[i] >> p;
if(p == -1) root = i;
else add(p, i);
}
dfs(root);
cout << f[root][m] << endl;
return 0;
}
背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7 的结果。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
思路及代码
用f[i, j]表示从前i个物品中选,体积恰好为j的方案总价值
g[i, j]表示前i个物品,体积恰好为j的,总价值为f[i, j]的方案数
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];
int main() {
cin >> n >> m;
memset(f, -0x3f, sizeof f);
f[0] = 0;
g[0] = 1;
for(int i = 0; i < n; i ++ ) {
int v, w;
cin >> v >> w;
for(int j = m; j >= v; j --) {
int maxv = max(f[j], f[j - v] + w);
int cnt = 0;
if(maxv == f[j]) cnt += g[j];
if(maxv == f[j - v] + w) cnt += g[j - v];
g[j] = cnt % mod;
f[j] = maxv;
}
}
int res = 0;
for(int i = 0; i <= m; i ++ ) res = max(res, f[i]);
int cnt = 0;
for(int i = 0; i <= m; i ++ ) {
if(res == f[i]) cnt = (cnt + g[i]) % mod;
}
cout << cnt << endl;
return 0;
}
能量石
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si 秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去 Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N 行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x 是组别编号(从 1 开始),y 是可以获得的最大能量值。
数据范围
1≤T≤10,
1≤N≤100,
1≤Si≤100,
1≤Ei≤105,
0≤Li≤105
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
样例解释
在样例#1中,有 N=4个宝石。杜达可以选择的一个吃石头顺序是:
- 吃第四块石头。这需要 5 秒,并给他 80 单位的能量。
- 吃第二块石头。这需要 5 秒,并给他 5 单位的能量(第二块石头开始时具有 30 单位能量,55 秒后失去了 25 单位的能量)。
- 吃第三块石头。这需要 100 秒,并给他 20 单位的能量(第三块石头开始时具有 30 单位能量,10 秒后失去了 10 单位的能量)。
- 吃第一块石头。这需要 20 秒,并给他 0 单位的能量(第一块石头以 10 单位能量开始,110 秒后已经失去了所有的能量)。
他一共获得了 105 单位的能量,这是能获得的最大值,所以答案是 105。
在样本案例#2中,有 N=3 个宝石。
无论杜达选择吃哪块石头,剩下的两个石头的能量都会耗光。
所以他应该吃第三块石头,给他提供 8 单位的能量。
在样本案例#3中,有 N=2 个宝石。杜达可以:
- 吃第一块石头。这需要 12 秒,并给他 300 单位的能量。
- 吃第二块石头。这需要 5 秒,并给他 200 单位的能量(第二块石头随着时间的推移不会失去任何能量!)。
所以答案是 500。
思路及代码
对于吃的两块相邻的石头i和j,如果先吃i再吃j,那么获得的能量为
E
i
+
E
j
−
S
i
∗
L
j
E_i + E_j - S_i * L_j
Ei+Ej−Si∗Lj
如果先吃j再吃i,那么获得的能量为
E
i
+
E
j
−
S
j
∗
L
i
E_i + E_j - S_j * L_i
Ei+Ej−Sj∗Li
因此,如果要先吃i再吃j,那么必须满足:
S
i
∗
L
j
<
S
j
∗
L
i
S_i * L_j < S_j * L_i
Si∗Lj<Sj∗Li
即
S
i
L
i
<
S
j
L
j
\frac{S_i}{L_i} < \frac{S_j}{L_j}
LiSi<LjSj
我们可以按照这个对能量石进行排序,再对每个能量石判断是否应该吃,这个问题就变成了一个01背包问题。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 10010;
int n;
struct Stone {
int s, e, l;
bool operator< (const Stone &W) const {
return s * W.l < W.s * l;
}
}stone[N];
int f[N];
int main() {
int T;
cin >> T;
for(int C = 1; C <= T; C ++ ) {
int m = 0;
cin >> n;
for(int i = 0; i < n; i ++ ) {
int s, e, l;
cin >> s >> e >> l;
stone[i] = {s, e, l};
m += s;
}
sort(stone, stone + n);
memset(f, -0x3f, sizeof f);
f[0] = 0;
for(int i = 0; i < n; i ++ ) {
int s = stone[i].s, e = stone[i].e, l = stone[i].l;
for(int j = m; j >= s; j -- ) {
f[j] = max(f[j], f[j - s] + e - (j - s) * l);
}
}
int res = 0;
for(int i = 0; i <= m; i ++ ) res = max(res, f[i]);
printf("Case #%d: %d\n", C, res);
}
return 0;
}