背包问题——01背包问题

文章目录

通俗易懂的 0-1 背包问题讲解视频:带你学透0-1背包问题!| 关于背包问题,你不清楚的地方,这里都讲了!| 动态规划经典问题 | 数据结构与算法

题目来自洛谷题单背包问题


[NOIP2005 普及组] 采药

题目描述

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

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

输入格式

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

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

输出格式

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

样例 #1

样例输入 #1

170 3
271 100
369 1
41 2

样例输出 #1

13

提示

【数据范围】

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

【题目来源】

NOIP 2005 普及组第三题

题解

使用二维数组:

 1def main():
 2    T, M = map(int, input().split(" "))
 3    t, m = [0], [0]
 4    for _ in range(M):
 5        tn, mn = map(int, input().split(" "))
 6        t.append(tn)
 7        m.append(mn)
 8
 9    dp = [[0 for _ in range(T + 1)] for _ in range(M + 1)]
10    for i in range(1, M + 1):
11        for j in range(1, T + 1):
12            if t[i] > j:
13                dp[i][j] = dp[i - 1][j]
14            else:
15                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - t[i]] + m[i])
16    print(dp[-1][-1])
17
18
19if __name__ == "__main__":
20    main()

使用一维滚动数组:

 1def main():
 2    T, M = map(int, input().split(" "))
 3    t, m = [0], [0]
 4    for _ in range(M):
 5        tn, mn = map(int, input().split(" "))
 6        t.append(tn)
 7        m.append(mn)
 8
 9    dp = [0 for _ in range(T + 1)]
10    for i in range(1, M + 1):
11        for j in reversed(range(t[i], T + 1)):
12            dp[j] = max(dp[j], dp[j - t[i]] + m[i])
13    print(dp[-1])
14
15
16if __name__ == "__main__":
17    main()

[NOIP2001 普及组] 装箱问题

题目描述

有一个箱子容量为 $V$,同时有 $n$ 个物品,每个物品有一个体积。

现在从 $n$ 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。

输入格式

第一行共一个整数 $V$,表示箱子容量。

第二行共一个整数 $n$,表示物品总数。

接下来 $n$ 行,每行有一个正整数,表示第 $i$ 个物品的体积。

输出格式

  • 共一行一个整数,表示箱子最小剩余空间。

样例 #1

样例输入 #1

124
26
38
43
512
67
79
87

样例输出 #1

10

提示

对于 $100%$ 数据,满足 $0<n \le 30$,$1 \le V \le 20000$。

【题目来源】

NOIP 2001 普及组第四题

题解

 1def main():
 2    V = int(input())
 3    n = int(input())
 4    v = [0]
 5    for _ in range(n):
 6        v.append(int(input()))
 7
 8    dp = [[0 for _ in range(V + 1)] for _ in range(n + 1)]
 9    for i in range(1, n + 1):
10        for j in range(1, V + 1):
11            if v[i] > j:
12                dp[i][j] = dp[i - 1][j]
13            else:
14                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + v[i])
15    print(V - dp[-1][-1])
16
17
18if __name__ == "__main__":
19    main()

小A点菜

题目背景

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

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

题目描述

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

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

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

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

输入格式

第一行是两个数字,表示 $N$ 和 $M$。

第二行起 $N$ 个正数 $a_i$(可以有相同的数字,每个数字均在 $1000$ 以内)。

输出格式

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

样例 #1

样例输入 #1

14 4
21 1 2 2

样例输出 #1

13

提示

2020.8.29,增添一组 hack 数据 by @yummy

题解

 1def main():
 2    N, M = map(int, input().split(" "))
 3    a = [0] + list(map(int, input().split(" ")))
 4
 5    dp = [[0 for _ in range(M + 1)] for _ in range(N + 1)]
 6    for i in range(1, N + 1):
 7        for j in range(1, M + 1):
 8            if j == a[i]:
 9                dp[i][j] = dp[i - 1][j] + 1
10            elif j < a[i]:
11                dp[i][j] = dp[i - 1][j]
12            else:
13                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - a[i]]
14    print(dp[-1][-1])
15
16
17if __name__ == "__main__":
18    main()

[NOIP2006 普及组] 开心的金明

题目描述

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

设第$j$件物品的价格为 $v_j$,重要度为 $w_j$,共选中了 $k$ 件物品,编号依次为 $j_1,j_2,…,j_k$,则所求的总和为:

$$v_{j_1} \times w_{j_1}+v_{j_2} \times w_{j_2} …+v_{j_k} \times w_{j_k}$$

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

输入格式

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

从第 $2$ 行到第 $m+1$ 行,第 $j$ 行给出了编号为 $j-1$ 的物品的基本数据,每行有 $2$ 个非负整数 $v,p$(其中 $v$ 表示该物品的价格 $(v \le 10000)$,$p$ 表示该物品的重要度($1\le p\le5$)。

输出格式

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

样例 #1

样例输入 #1

11000 5
2800 2
3400 5
4300 5
5400 3
6200 2

样例输出 #1

13900

提示

NOIP 2006 普及组 第二题

题解

 1def main():
 2    n, m = map(int, input().split(" "))
 3    v, w = [0], [0]
 4    for _ in range(m):
 5        vi, wi = map(int, input().split(" "))
 6        v.append(vi)
 7        w.append(wi)
 8
 9    dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
10    for i in range(m + 1):
11        for j in range(n + 1):
12            if j < v[i]:
13                dp[i][j] = dp[i - 1][j]
14            else:
15                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + v[i] * w[i])
16    print(dp[-1][-1])
17
18
19if __name__ == "__main__":
20    main()

不开心的金明

题目描述

金明今天很不开心,家里购置的二手房就要领钥匙了,房里并没有一间他自己专用的很宽敞的房间。更让他不高兴的是,妈妈昨天对他说:“你需要购买哪些物品,怎么布置,你说了不算(有很大的限制),而且不超过 $W$ 元钱。”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 $W$ 元。于是,他把每件物品规定了一个重要度整数 $p_i$ 表示。他还从因特网上查到了每件物品的价格 $v_i$(都是整数元)。

妈妈看到购物单后进行了审查,要求购物单上所有的物品价格的极差(最贵的减去最便宜的)不超过 $3$(当然金明至今不知道为什么会这样)。他希望在不超过 $W$ 元(可以等于 $W$ 元)的前提下,使购买的重要度总和 $\sum p_i$ 的最大。

请你帮助金明设计一个满足要求的购物单,你只需要告诉我们重要度的最大的和。

输入格式

输入的第 $1$ 行,为两个正整数,用一个空格隔开:

$n,W$(其中 $W$ 表示总钱数,$n$ 为希望购买物品的个数。)

从第 $2$ 行到第 $n+1$ 行,第 $j$ 行给出了编号为 $j-1$ 的物品的基本数据,每行有 $2$ 个非负整数 $v$ $p$(其中 $v$ 表示该物品的价格,$p$ 表示该物品的重要度)

输出格式

输出只有一个正整数,为不超过总钱数的物品的重要度的总和的最大值。

样例 #1

样例输入 #1

15 10
22 800
35 400
45 300
53 400
62 200

样例输出 #1

11600

提示

$1 \le N \le 100$。

$1 \le W \le 10^9$。

$1 \le v_i \le 10^9$。

对所有的 $i=1,2,3,…,N$,$\min(v_i) \le v_i \le \min(v_i)+3$。

$1 \le p_i \le 10^7$。

题解

这道题依然是一个 0-1 背包问题,和前一题的区别在于,这道题给出的背包容量(即金额限制)太大了,可能有 $10^9$ 之多,依照常规二位数组或者一维滚动数组做法必定会导致部分测试点 TLE 或 MLE。 但是经过观察题目条件我们知道,所有物品价格的极差不超过 $3$,我们不妨将所有物品的价格同时减去输入中给出的最小价格,这样所有的价格都可以以 $1$ $2$ $3$ $4$ 来表示,以此缩小 dp 数组。 在这种情况下,每购买一件物品的实际价格就相当于处理后的价格加上最低价格,那么购买 $n$ 件物品的实际价格就相当于处理后的价格加上 $n$ 倍最低价格。 因此,定义动态规划数组 dp[i][j][k] 表示在前 i 种物品种,总钱数(处理后)为 j,已购买了 k 件物品时的最大重要度。 此时 vmin * k + j 表示实际金额,而状态转移方程为 dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - v[i]][k - 1] + p[i]);

 1import java.util.Scanner;
 2import java.util.Arrays;
 3
 4public class Main {
 5    public static void main(String[] args) {
 6        Scanner scanner = new Scanner(System.in);
 7        int N = scanner.nextInt();
 8        int W = scanner.nextInt();
 9
10        int[] v = new int[N + 1];
11        int[] p = new int[N + 1];
12        for (int i = 1; i <= N; ++i) {
13            v[i] = scanner.nextInt();
14            p[i] = scanner.nextInt();
15        }
16        scanner.close();
17
18        int vmin = Arrays.stream(v, 1, N + 1).min().getAsInt();
19        int vsum = 0;
20        for (int i = 1; i <= N; ++i) {
21            v[i] -= vmin;
22            vsum += v[i];
23        }
24
25        int[][][] dp = new int[N + 1][vsum + 1][N + 1];
26        for (int i = 1; i <= N; ++i) {
27            for (int j = 0; j <= vsum; ++j) {  // j 从 0 开始计数,因为处理后的价格为 0 1 2 3
28                for (int k = 1; k <= i; ++k) {
29                    if (W >= vmin * k + j && j >= v[i]) {
30                        dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - v[i]][k - 1] + p[i]);
31                    } else {
32                        dp[i][j][k] = dp[i - 1][j][k];
33                    }
34                }
35            }
36        }
37
38        int res = 0;
39        for (int j = 0; j <= vsum; ++j) { // 这里的 j 也从 0 开始计数
40            for (int k = 1; k <= N; ++k) {
41                res = Math.max(dp[N][j][k], res);
42            }
43        }
44        System.out.println(res);
45    }
46}

相关系列文章