Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Knapsack 2

Knapsack 2
Nguồn: CLUE OJ – Chương 22: Quy hoạch động (VNOJ – Educational DP E) Độ khó: Khá Quy hoạch động Cái túi theo giá trị

Đề bài

Có $N$ đồ vật được đánh số từ $1$ đến $N$. Đồ vật thứ $i$ $(1 \le i \le N)$ có khối lượng là $w_i$ và giá trị là $v_i$.

Taro quyết định chọn một số đồ vật bỏ vào chiếc túi của anh mang về. Sức chứa tối đa của chiếc túi là $W$, nghĩa là tổng khối lượng các đồ vật có thể mang tối đa là $W$.

Hãy tìm tổng giá trị các đồ vật lớn nhất mà Taro có thể mang về nhà.

Input

  • Dòng đầu tiên chứa hai số nguyên $N$ $(1 \le N \le 100)$ và $W$ $(1 \le W \le 10^9)$: số lượng đồ vật và sức chứa tối đa của chiếc túi.
  • $N$ dòng tiếp theo, mỗi dòng chứa hai số nguyên $w_i$ $(1 \le w_i \le W)$ và $v_i$ $(1 \le v_i \le 10^3)$ lần lượt là khối lượng và giá trị của đồ vật thứ $i$.

Output: xuất ra tổng giá trị các đồ vật lớn nhất mà Taro có thể mang về nhà.

Giới hạn trên CLUE OJ: 2 giây, bộ nhớ 1 GB.

Ví dụ 1 – input
3 8
3 30
4 50
5 60
Ví dụ 1 – output
90
Ví dụ 2 – input
1 1000000000
1000000000 10
Ví dụ 2 – output
10
Ví dụ 3 – input
6 15
6 5
5 6
6 4
6 6
3 5
7 2
Ví dụ 3 – output
17

Giải thích: ví dụ 1 chọn đồ vật 1 và 3 (khối lượng $3 + 5 = 8$, giá trị $30 + 60 = 90$); ví dụ 3 chọn đồ vật 2, 4, 5 (khối lượng $5 + 6 + 3 = 14$, giá trị $6 + 6 + 5 = 17$).

Lời mở đầu

Đề giống hệt Knapsack 1, chỉ đổi giới hạn: $W$ lên tới $10^9$ nhưng giá trị $v_i \le 10^3$. Chỉ một thay đổi nhỏ đó làm cách QHĐ theo khối lượng sụp đổ hoàn toàn (mảng $10^9$ phần tử!). Bài học của bài này rất quan trọng: khi một chiều trạng thái quá lớn, hãy thử đổi vai – lấy đại lượng nhỏ làm chỉ số mảng, đại lượng lớn làm giá trị lưu trong mảng.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test chính thức có gì?

Test$N$$W$$v_i$ lớn nhất$\sum v_i$ lớn nhấtĐáp án lớn nhất
1–203–186–96100968449
21–80100–100119.865.234–984.077.80799925.50225.195
81–100100–1001.000.000.000–1.000.000.00098627.76425.375

Giống Knapsack 1, bộ test có 2 mức: 20 test đầu rất nhỏ ($W < 100$), 80 test sau $N = 100$ và $W$ rất lớn (20 test cuối $W = 10^9$). Điểm mấu chốt nằm ở cột $\sum v_i$: tổng giá trị mọi đồ vật chỉ khoảng $2{,}8 \cdot 10^4$ (lý thuyết tối đa $100 \cdot 10^3 = 10^5$) – nhỏ hơn $W$ cả chục nghìn lần.

MứcĐiều kiệnTestCách 1
vét cạn
Cách 2
QHĐ theo khối lượng
Cách 3
QHĐ theo giá trị
1$N \le 18,\ W \le 96$1–20quaquaqua
2$N = 100,\ W \le 10^9$21–100quá thời gianhết bộ nhớ / quá thời gianqua (0,01 s)

Ý tưởngĐổi vai: dp[s] = khối lượng nhỏ nhất để đạt giá trị s

Ở Knapsack 1 ta hỏi: "với sức chứa $c$, được giá trị lớn nhất là bao nhiêu?". Giờ ta hỏi ngược lại: "muốn có đúng tổng giá trị $s$ thì cần khối lượng nhỏ nhất là bao nhiêu?". Tổng giá trị tối đa chỉ $S = \sum v_i \le 10^5$ nên mảng có $S + 1$ ô là đủ.

Công thức
Gọi dp[i][s] = khối lượng nhỏ nhất khi chọn trong $i$ đồ vật đầu để tổng giá trị đúng bằng $s$ ($\infty$ nếu không thể). $$dp[i][s] = \min\big(dp[i-1][s],\ dp[i-1][s - v_i] + w_i\big)$$ Cơ sở: $dp[0][0] = 0$, $dp[0][s] = \infty$ với $s > 0$. Đáp án là $s$ lớn nhất sao cho $dp[N][s] \le W$.

Em nhập $W$ và các đồ vật (giá trị nhỏ thôi để bảng vừa màn hình), bấm Tiếp để xem bảng điền từng ô, bước cuối dò hàng cuối tìm giá trị lớn nhất mà khối lượng không vượt $W$:

ô đang tínhô trên (không lấy)ô chéo (lấy)khối lượng ≤ W
Ví dụ nhanh:

Cách 1Vét cạn lấy / không lấy (mức 1)

Vẫn là quay lui như Knapsack 1, chỉ cần nhớ tổng khối lượng có thể vượt int ($100 \cdot 10^9$) nên dùng long long.

C++▶ Chạy tay từng dòng codeCách 1: Try(i, sw, sv) rẽ nhánh không lấy / lấy.
Dữ liệu mẫu:
Khung bên phải là ngăn xếp lời gọi.
Không gian$O(N)$
Thời gian$O(2^N)$

Kết quả chạy thật: đúng 20/100 test – qua 20 test nhỏ, 80 test $N = 100$ bị quá thời gian.

Cách 2Dùng lại QHĐ theo khối lượng của Knapsack 1 (mức 1)

Thử nộp nguyên lời giải mảng 1 chiều của Knapsack 1, xem chuyện gì xảy ra:

C++▶ Chạy tay từng dòng codeCách 2: QHĐ theo khối lượng – mảng dp có W + 1 ô (chỉ chạy được khi W nhỏ).
Dữ liệu mẫu:
Thử nhập W = 1000000000 sẽ thấy: mảng dp cần 10^9 ô, không thể mô phỏng – máy chấm cũng vậy.
Không gian$O(W)$
Thời gian$O(N \cdot W)$

Kết quả chạy thật: đúng 20/100 test. Với 20 test nhỏ thì vẫn đúng, nhưng khi $W$ cỡ $10^8$–$10^9$, chỉ riêng mảng dp đã cần $8 \cdot 10^9$ byte = 8 GB – vượt xa 1 GB (mình chạy với giới hạn bộ nhớ 1 GB thì chương trình chết ngay khi cấp phát), chưa kể $N \cdot W = 10^{11}$ phép tính. Cụ thể: 77 test bị lỗi cấp phát bộ nhớ, 3 test có $W$ khoảng $1{,}2 \cdot 10^8$ (mảng vừa đủ 1 GB) thì quá thời gian.

Cách 3QHĐ theo giá trị (full điểm)

Cài đặt công thức ở phần ý tưởng bằng một mảng 1 chiều dp[s], duyệt $s$ giảm dần (giống hệt mẹo ở Knapsack 1 để mỗi đồ vật chỉ được lấy 1 lần):

C++▶ Chạy tay từng dòng codeCách 3: dp[s] = khối lượng nhỏ nhất để đạt đúng giá trị s.
Dữ liệu mẫu:
Con trỏ s là ô đang xét, s-v là ô bên trái được đọc.
  1. INF = 1e18 đánh dấu "không đạt được"; cộng thêm $w_i \le 10^9$ vẫn không tràn long long.
  2. Kích thước mảng là $S + 1$ với $S = \sum v_i \le 10^5$ – chỉ cỡ 800 KB.
  3. Vòng cuối duyệt $s$ tăng dần, gặp dp[s] <= W thì cập nhật ans = s, nên cuối cùng ans là giá trị lớn nhất mang được.
Không gian$O(\sum v_i)$
Thời gian$O(N \cdot \sum v_i) \approx 10^7$

Kết quả chạy thật: bản C++ đúng 100/100 test, chậm nhất 0,01 giây. Bản Python (mình viết thêm, cũng dùng list comprehension cho cả đoạn) đúng 100/100 test, chậm nhất 0,29 giây.

Ghi chú của mình: trên bộ test chính thức (100 test, 2 giây, 1 GB): cách 1 vét cạn 20/100; cách 2 QHĐ theo khối lượng 20/100 (hết bộ nhớ khi $W$ lớn); cách 3 QHĐ theo giá trị 100/100 (0,01 s), bản Python 100/100. Mẹo "đổi vai trạng thái" này gặp rất nhiều: luôn nhìn giới hạn để chọn đại lượng nhỏ làm chỉ số mảng.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Nhận xét